Flutter中BottomSheet内setState无法更新变量值问题
问题根源解析
你碰到这个问题其实是Flutter路由机制的特性导致的:showModalBottomSheet 弹出的底部弹窗是一个独立的路由,它的组件树和你当前页面的 StatefulWidget 不在同一个上下文里。你在卡片点击时调用的 setState 只能触发原页面的UI刷新,根本管不到弹窗里的那个 Text 组件,所以它自然不会更新。
两种可行的修复方案
下面给你两种常用的解决办法,你可以根据自己的代码习惯选:
方案一:用 StatefulBuilder 给弹窗加局部状态
StatefulBuilder 可以在弹窗内部创建一个独立的状态作用域,让弹窗里的组件能响应内部的状态变化,同时也能同步更新原页面的全局变量:
void _callQuickListModalBottomSheet(context){ // 先把当前选中值同步到局部变量,方便弹窗内部使用 String currentSelected = _localPNumberSelected; Future<void> future = showModalBottomSheet<void>( context: context, builder: (BuildContext bc){ // 用StatefulBuilder包裹整个弹窗内容 return StatefulBuilder( builder: (context, sheetSetState) { return Container( height: 280, margin: const EdgeInsets.fromLTRB(200, 0, 10, 0), child: Container( decoration: BoxDecoration( color: const Color.fromRGBO(36, 46, 66, 1), borderRadius: const BorderRadius.only( topLeft: Radius.circular(20), topRight: Radius.circular(20) ), ), child: Column( children: <Widget>[ Container( margin: const EdgeInsets.fromLTRB(10, 10, 10, 0), height:45, child: Column( children: <Widget>[ Card( color: Colors.white, child: Row ( children: <Widget>[ Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ Container( decoration: BoxDecoration( color: Colors.green, borderRadius: const BorderRadius.only( topLeft: Radius.circular(5), bottomLeft: Radius.circular(5) ), ), child: const Icon(Icons.check, size: 20.0), height: 27, width: 30, ), ], ), Expanded( // 避免Text溢出 child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ // 使用局部的currentSelected变量 Text(currentSelected), ], ), ), ], ) ) ] ) ), Container( height:190, child: ListView.builder( shrinkWrap: true, itemCount: vehicleListdata.length, itemBuilder: (BuildContext ctxt, int index) { return Container( margin: const EdgeInsets.fromLTRB(10, 0, 10, 0), height: 35.0, child: Card( color: Colors.white, child: Row ( children: <Widget>[ Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ Container( decoration: BoxDecoration( color: Colors.amber, borderRadius: const BorderRadius.only( topLeft: Radius.circular(5), bottomLeft: Radius.circular(5) ), ), height: 27, width: 10, ), ], ), Expanded( child: GestureDetector( onTap: () { final newNumber = vList[index]["pNumber"].toString(); // 更新弹窗内部状态,触发Text刷新 sheetSetState(() { currentSelected = newNumber; }); // 同步更新原页面全局变量 setState(() { _localPNumberSelected = newNumber; }); doSomething(newNumber); }, child: Text(vList[index]["pNumber"].toString()), ), ), ], ), ) ); } ) ) ], ), ) ); }, ); } ); future.then((void value) => _closeModal(value)); }
方案二:用 ValueNotifier 实现跨组件状态监听
如果你的项目里需要更灵活的状态共享,或者这个选中值还要在其他地方使用,推荐用 ValueNotifier + ValueListenableBuilder 的组合:
第一步:在你的State类里定义ValueNotifier
// 定义一个ValueNotifier来管理选中的号码 final ValueNotifier<String> _selectedNumberNotifier = ValueNotifier(''); @override void initState() { super.initState(); // 初始化时把当前选中值赋值给Notifier _selectedNumberNotifier.value = _localPNumberSelected; } @override void dispose() { // 记得销毁Notifier,避免内存泄漏 _selectedNumberNotifier.dispose(); super.dispose(); }
第二步:修改弹窗代码
void _callQuickListModalBottomSheet(context){ Future<void> future = showModalBottomSheet<void>( context: context, builder: (BuildContext bc){ return Container( height: 280, margin: const EdgeInsets.fromLTRB(200, 0, 10, 0), child: Container( decoration: BoxDecoration( color: const Color.fromRGBO(36, 46, 66, 1), borderRadius: const BorderRadius.only( topLeft: Radius.circular(20), topRight: Radius.circular(20) ), ), child: Column( children: <Widget>[ Container( margin: const EdgeInsets.fromLTRB(10, 10, 10, 0), height:45, child: Column( children: <Widget>[ Card( color: Colors.white, child: Row ( children: <Widget>[ Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ Container( decoration: BoxDecoration( color: Colors.green, borderRadius: const BorderRadius.only( topLeft: Radius.circular(5), bottomLeft: Radius.circular(5) ), ), child: const Icon(Icons.check, size: 20.0), height: 27, width: 30, ), ], ), Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ // 用ValueListenableBuilder监听Notifier的变化 ValueListenableBuilder<String>( valueListenable: _selectedNumberNotifier, builder: (context, currentValue, child) { return Text(currentValue); }, ), ], ), ), ], ) ) ] ) ), Container( height:190, child: ListView.builder( shrinkWrap: true, itemCount: vehicleListdata.length, itemBuilder: (BuildContext ctxt, int index) { return Container( margin: const EdgeInsets.fromLTRB(10, 0, 10, 0), height: 35.0, child: Card( color: Colors.white, child: Row ( children: <Widget>[ Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ Container( decoration: BoxDecoration( color: Colors.amber, borderRadius: const BorderRadius.only( topLeft: Radius.circular(5), bottomLeft: Radius.circular(5) ), ), height: 27, width: 10, ), ], ), Expanded( child: GestureDetector( onTap: () { final newNumber = vList[index]["pNumber"].toString(); // 更新Notifier的值,自动触发所有监听它的UI刷新 _selectedNumberNotifier.value = newNumber; // 同步更新原页面全局变量 setState(() { _localPNumberSelected = newNumber; }); doSomething(newNumber); }, child: Text(vList[index]["pNumber"].toString()), ), ), ], ), ) ); } ) ) ], ), ) ); } ); future.then((void value) => _closeModal(value)); }
额外小提示
我还给你加了几个小优化:
- 给文本所在的Column加了
Expanded,避免文本过长时溢出; - 去掉了一些冗余的
new关键字(Flutter 2.0+可以省略); - 给常量用了
const修饰,提升性能。
内容的提问来源于stack exchange,提问作者newbie
相关产品推荐
相关产品推荐

