You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 04:45:39