Flutter中DraggableScrollableSheet与setState配合异常问题求助
问题解决:DraggableScrollableSheet中setState导致弹窗关闭的问题
问题根源
你遇到的核心矛盾是:调用父Widget的setState会触发整个Widget树重建,导致DraggableScrollableSheet的状态被重置,当前弹窗直接关闭;但如果不调用setState,UI无法刷新,加载指示器就无法显示。
解决方案:局部状态管理
通过StatefulBuilder实现局部UI刷新,把加载状态的更新范围限定在按钮区域内,不会影响整个DraggableScrollableSheet的状态。
修改后的代码示例
将原按钮包裹在StatefulBuilder中,将isLoading作为局部状态管理:
StatefulBuilder( builder: (context, setState) { bool isLoading = false; return InkWell( onTap: () { setState(() { isLoading = true; // 仅刷新当前按钮区域,不会关闭Sheet }); sendEmail(widget.measure).then((sendEmail) { if (sendEmail == true) { setState(() { isLoading = false; }); newTransmissionController.reset(); transmissionDoneController.animateTo( 0.95, duration: const Duration(milliseconds: 100), curve: Curves.easeOutBack, ); } else { setState(() { isLoading = false; }); Navigator.of(context).pop(); Navigator.of(context).push(PageRouteBuilder( pageBuilder: (context, animation1, animation2) => const TransmissionErrorPage(), )); } }); }, child: AnimatedContainer( duration: const Duration(milliseconds: 400), height: MediaQuery.of(context).size.height * 0.08, margin: const EdgeInsets.symmetric(horizontal: 5), width: MediaQuery.of(context).size.height * 0.18, decoration: BoxDecoration( color: const Color(0xFF008DFF), borderRadius: BorderRadius.circular(10), ), child: Center( child: !isLoading ? const Text( "Yes", style: TextStyle( color: Colors.white, fontSize: 17, fontFamily: 'SFProRegular', ), ) : const Center( child: CircularProgressIndicator( backgroundColor: Colors.white, valueColor: AlwaysStoppedAnimation<Color>(Color(0xFF008DFF)), ), ), ), ), ); }, )
备选方案:封装独立按钮Widget
如果按钮需要复用,可以将其封装为独立的StatefulWidget,内部管理加载状态:
class ConfirmButton extends StatefulWidget { final Future<bool> Function() sendEmail; final VoidCallback onSuccess; final VoidCallback onError; const ConfirmButton({ super.key, required this.sendEmail, required this.onSuccess, required this.onError, }); @override State<ConfirmButton> createState() => _ConfirmButtonState(); } class _ConfirmButtonState extends State<ConfirmButton> { bool isLoading = false; @override Widget build(BuildContext context) { return InkWell( onTap: () { setState(() => isLoading = true); widget.sendEmail().then((success) { setState(() => isLoading = false); success ? widget.onSuccess() : widget.onError(); }); }, child: AnimatedContainer( // 保持原有的样式代码不变 duration: const Duration(milliseconds: 400), height: MediaQuery.of(context).size.height * 0.08, margin: const EdgeInsets.symmetric(horizontal: 5), width: MediaQuery.of(context).size.height * 0.18, decoration: BoxDecoration( color: const Color(0xFF008DFF), borderRadius: BorderRadius.circular(10), ), child: Center( child: !isLoading ? const Text( "Yes", style: TextStyle( color: Colors.white, fontSize: 17, fontFamily: 'SFProRegular', ), ) : const Center( child: CircularProgressIndicator( backgroundColor: Colors.white, valueColor: AlwaysStoppedAnimation<Color>(Color(0xFF008DFF)), ), ), ), ), ); } }
使用时直接传入回调:
ConfirmButton( sendEmail: () => sendEmail(widget.measure), onSuccess: () { newTransmissionController.reset(); transmissionDoneController.animateTo( 0.95, duration: const Duration(milliseconds: 100), curve: Curves.easeOutBack, ); }, onError: () { Navigator.of(context).pop(); Navigator.of(context).push(PageRouteBuilder( pageBuilder: (context, animation1, animation2) => const TransmissionErrorPage(), )); }, )
核心逻辑
两种方案都是通过缩小状态更新的范围,避免触发父Widget的整体重建,既实现了加载指示器的动态显示,又能维持DraggableScrollableSheet的当前状态,不会导致弹窗意外关闭。
内容的提问来源于stack exchange,提问作者compte profe
相关产品推荐
相关产品推荐

