Flutter调用setState未触发ModalBottomSheet组件重建问题求助
问题核心原因
showModalBottomSheet属于独立的路由页面,你调用父组件GenresPicker的setState只会触发父组件自身重绘,不会刷新已经打开的弹窗内部UI。- 你注释掉的直接修改
_selectedGenres的写法,虽然没有触发父组件重绘,但是下次打开弹窗时,新的弹窗实例会读取最新的_selectedGenres值,所以能看到选中状态,但点击当下弹窗不会立刻变色。 - 额外注意:如果你的
Genre是自定义类,没有重写==运算符和hashCode,_selectedGenres.contains()判断的是实例内存地址而非值相等。如果fetchGenres()每次返回的是全新的实例,就算内容一致也会判断为未选中,导致下次打开弹窗状态丢失。
修复方案
我们用 StatefulBuilder 包裹弹窗内部内容,让弹窗可以自己触发局部刷新,同时同步更新父组件的选中列表:
class GenresPicker extends StatefulWidget { @override _GenresPickerState createState() => _GenresPickerState(); } class _GenresPickerState extends State<GenresPicker> { List<Genre> _selectedGenres = []; // 新增参数支持传入弹窗的setState实现局部刷新 void addGenre(Genre genre, [StateSetter? modalSetState]) { setState(() { if (!_selectedGenres.contains(genre)) { _selectedGenres.add(genre); } }); // 弹窗内调用时同时刷新弹窗UI modalSetState?.call(() {}); } @override void initState() { super.initState(); } @override Widget build(BuildContext context) { return Container( alignment: Alignment.center, padding: EdgeInsets.all(20), child: FutureBuilder<List<Genre>>( future: fetchGenres(), builder: (context, snapshot) { if (snapshot.hasData) { return Container( alignment: Alignment.center, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( "MODAL BOTTOM SHEET EXAMPLE", style: TextStyle( fontStyle: FontStyle.italic, letterSpacing: 0.4, fontWeight: FontWeight.w600), ), SizedBox( height: 20, ), ElevatedButton( onPressed: () { showModalBottomSheet( context: context, builder: (context) { // 套StatefulBuilder拿到弹窗自身的状态更新方法 return StatefulBuilder( builder: (context, modalSetState) { return Column( children: <Widget>[ Text("Genres:"), Expanded( child: GridView.builder( gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 100, childAspectRatio: 4 / 2, crossAxisSpacing: 10, mainAxisSpacing: 10), itemCount: snapshot.data!.length, itemBuilder: (BuildContext ctx, index) { final genre = snapshot.data![index]; return Container( alignment: Alignment.center, child: GestureDetector( onTap: () { if (!_selectedGenres.contains(genre)) { addGenre(genre, modalSetState); } }, child: Text(genre.name), ), decoration: BoxDecoration( color: _selectedGenres.contains(genre) ? Colors.blue[200] : Colors.blue[50], borderRadius: BorderRadius.circular(15)), ); }, ), ), ], ); }, ); }, ); }, child: Text( 'Click Me', style: TextStyle( color: Colors.white, fontWeight: FontWeight.w600, letterSpacing: 0.6), ), ), ], ), ); } else if (snapshot.hasError) { return Text("${snapshot.error}"); } return Center(child: CircularProgressIndicator()); }, ), ); } } // 自定义Genre类需要补充重写==和hashCode,避免contains判断失效,示例: // class Genre { // final int id; // final String name; // Genre({required this.id, required this.name}); // // @override // bool operator ==(Object other) => // identical(this, other) || // other is Genre && runtimeType == other.runtimeType && id == other.id; // // @override // int get hashCode => id.hashCode; // }
内容的提问来源于stack exchange,提问作者SaltyCode
相关产品推荐
相关产品推荐

