Flutter模态弹窗内Radio单选按钮点击无响应问题求助
问题根因
弹窗内Radio点击无响应是两个逻辑错误共同导致的:
difficulty变量声明在showModalBottomSheet的builder函数内部,每次builder重新执行时变量都会被重置为初始的null,选中状态根本无法留存- 你在Radio的onChanged回调里调用的是父页面
_HomeState的setState,这个方法只会触发父页面重绘,而showModalBottomSheet生成的是独立于父页面的单独路由,有自己独立的渲染上下文,外部的setState无法通知弹窗内部组件刷新,就算变量值发生变化UI也不会同步更新
修复方案
最易维护的实现是把弹窗内的状态管理和父页面解耦,给弹窗内容单独封装为StatefulWidget,使用弹窗自身的setState触发UI更新;如果需要把选中的难度值传回父页面,在选中后通过pop方法回传参数即可。
修复后的完整可运行代码:
import 'package:flutter/material.dart'; import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; class Home extends StatefulWidget { const Home({Key? key}) : super(key: key); @override State<Home> createState() => _HomeState(); } class _HomeState extends State<Home> { // 父页面需要使用选中值时,可在此声明变量存储 String? selectedDifficulty; @override Widget build(BuildContext context) { return Scaffold( body: Center( child: TextButton( onPressed: () async { // 接收弹窗关闭时回传的选中结果 final result = await showModalBottomSheet( context: context, builder: (context) => const DifficultySelector(), ); if(result != null) { setState(() { selectedDifficulty = result; }); } }, child: const Text("Create Bubble")), )); } } // 单独封装弹窗内的选择组件,管理自身交互状态 class DifficultySelector extends StatefulWidget { const DifficultySelector({Key? key}) : super(key: key); @override State<DifficultySelector> createState() => _DifficultySelectorState(); } class _DifficultySelectorState extends State<DifficultySelector> { String? difficulty; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only( left: 20, right: 20, top: 20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, // 弹窗高度自适应内容,避免默认占满全屏 children: [ const Text( "Difficulty", style: TextStyle(fontSize: 20), ), ListTile( title: const Text("easy"), leading: Radio( value: 'easy', groupValue: difficulty, onChanged: (value) { setState(() { difficulty = value; }); // 选中后延迟200ms关闭弹窗,给用户明确的选中反馈后再回传值 Future.delayed(const Duration(milliseconds: 200), () { Navigator.pop(context, value); }); }), ), ListTile( title: const Text("medium"), leading: Radio( value: 'medium', groupValue: difficulty, onChanged: (value) { setState(() { difficulty = value; }); Future.delayed(const Duration(milliseconds: 200), () { Navigator.pop(context, value); }); }), ) ]), ); } }
额外说明
- 如果不想单独封装组件,也可以在弹窗builder的最外层包裹
StatefulBuilder,用它提供的setState方法刷新弹窗内部UI,不过从长期代码维护的角度,单独抽离组件的可读性和可扩展性更好 - 不要把弹窗内需要持久化的交互状态声明在builder函数作用域内,否则每次组件重绘状态都会被重置为初始值
内容的提问来源于stack exchange,提问作者Marko
相关产品推荐
相关产品推荐

