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

Flutter中showModalBottomSheet内TimePicker setState不更新问题

问题分析

你遇到的核心问题有三点:

  1. openAt/closeAt是test()函数的局部变量,不属于任何组件的状态,修改它们不会触发UI重建。
  2. 你调用的setState()是外层组件的状态更新方法,但模态框的UI是独立渲染的,外层setState不会刷新模态框内部内容。
  3. 你在setState里只写了openAt;,这行代码没有实际作用,无法触发状态更新。
两种解决方案

方案一:使用StatefulBuilder快速实现

不需要拆分组件,直接在模态框内部通过StatefulBuilder获取能刷新模态框UI的状态更新方法:

test() {
  showModalBottomSheet<void>(
    isScrollControlled: true,
    isDismissible: false,
    enableDrag: false,
    barrierColor: Colors.black.withOpacity(0.1),
    backgroundColor: Colors.white,
    shape: const RoundedRectangleBorder(
      borderRadius: BorderRadius.only(
        topLeft: Radius.circular(15),
        topRight: Radius.circular(15),
      ),
    ),
    context: context,
    builder: (BuildContext context) {
      String openAt = "00:00";
      String closeAt = "00:00";
      Size size = MediaQuery.of(context).size;
      
      // 用StatefulBuilder包裹内容,获取模态框内部的setState方法
      return StatefulBuilder(
        builder: (context, setStateModal) {
          return Padding(
            padding: MediaQuery.of(context).viewInsets,
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    Row(
                      mainAxisAlignment: MainAxisAlignment.end,
                      children: [
                        IconButton(
                          onPressed: () => context.pop(),
                          icon: Icon(Icons.close, color: Styles.blackColor),
                        )
                      ],
                    ),
                    s_height,
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: [
                        Text("Open at", style: TextStyle(fontWeight: FontWeight.w600)),
                        GestureDetector(
                          onTap: () {
                            showTimePicker(
                              context: context,
                              initialTime: TimeOfDay.now(),
                            ).then((value) {
                              if (value != null) {
                                // 格式化时间为hh:mm格式
                                openAt = "${value.hour.toString().padLeft(2, '0')}:${value.minute.toString().padLeft(2, '0')}";
                                // 触发模态框内部UI更新
                                setStateModal(() {});
                              }
                            });
                          },
                          child: Container(
                            decoration: BoxDecoration(
                              color: Styles.greyLightColor,
                              borderRadius: BorderRadius.all(Radius.circular(10)),
                            ),
                            padding: EdgeInsets.all(7.0),
                            child: Text(openAt),
                          ),
                        )
                      ],
                    ),
                    Divider(),
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: [
                        Text("Close at", style: TextStyle(fontWeight: FontWeight.w600)),
                        GestureDetector(
                          onTap: () {
                            showTimePicker(
                              context: context,
                              initialTime: TimeOfDay.now(),
                            ).then((value) {
                              if (value != null) {
                                closeAt = "${value.hour.toString().padLeft(2, '0')}:${value.minute.toString().padLeft(2, '0')}";
                                setStateModal(() {});
                              }
                            });
                          },
                          child: Container(
                            decoration: BoxDecoration(
                              color: Styles.greyLightColor,
                              borderRadius: BorderRadius.all(Radius.circular(10)),
                            ),
                            padding: EdgeInsets.all(7.0),
                            child: Text(closeAt),
                          ),
                        )
                      ],
                    ),
                    s_height,
                    Container(
                      width: size.width * 0.8,
                      child: ElevatedButton(
                        style: ButtonStyle(
                          foregroundColor: MaterialStateProperty.all<Color>(Styles.whiteColor),
                          backgroundColor: MaterialStateProperty.all<Color>(Styles.blackColor),
                        ),
                        onPressed: () => context.pop({"openAt": openAt, "closeAt": closeAt}),
                        child: Text("Save"),
                      ),
                    )
                  ],
                ),
              ),
            ),
          );
        },
      );
    },
  );
}

方案二:拆分为独立的StatefulWidget(推荐复杂场景)

把模态框内容抽成单独的组件,代码结构更清晰,便于维护:

1. 定义时间选择模态框组件

class TimePickerBottomSheet extends StatefulWidget {
  const TimePickerBottomSheet({super.key});

  @override
  State<TimePickerBottomSheet> createState() => _TimePickerBottomSheetState();
}

class _TimePickerBottomSheetState extends State<TimePickerBottomSheet> {
  String openAt = "00:00";
  String closeAt = "00:00";

  @override
  Widget build(BuildContext context) {
    Size size = MediaQuery.of(context).size;
    return Padding(
      padding: MediaQuery.of(context).viewInsets,
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Padding(
          padding: const EdgeInsets.all(8.0),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Row(
                mainAxisAlignment: MainAxisAlignment.end,
                children: [
                  IconButton(
                    onPressed: () => context.pop(),
                    icon: Icon(Icons.close, color: Styles.blackColor),
                  )
                ],
              ),
              s_height,
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Text("Open at", style: TextStyle(fontWeight: FontWeight.w600)),
                  GestureDetector(
                    onTap: () {
                      showTimePicker(
                        context: context,
                        initialTime: TimeOfDay.now(),
                      ).then((value) {
                        if (value != null) {
                          setState(() {
                            openAt = "${value.hour.toString().padLeft(2, '0')}:${value.minute.toString().padLeft(2, '0')}";
                          });
                        }
                      });
                    },
                    child: Container(
                      decoration: BoxDecoration(
                        color: Styles.greyLightColor,
                        borderRadius: BorderRadius.all(Radius.circular(10)),
                      ),
                      padding: EdgeInsets.all(7.0),
                      child: Text(openAt),
                    ),
                  )
                ],
              ),
              Divider(),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Text("Close at", style: TextStyle(fontWeight: FontWeight.w600)),
                  GestureDetector(
                    onTap: () {
                      showTimePicker(
                        context: context,
                        initialTime: TimeOfDay.now(),
                      ).then((value) {
                        if (value != null) {
                          setState(() {
                            closeAt = "${value.hour.toString().padLeft(2, '0')}:${value.minute.toString().padLeft(2, '0')}";
                          });
                        }
                      });
                    },
                    child: Container(
                      decoration: BoxDecoration(
                        color: Styles.greyLightColor,
                        borderRadius: BorderRadius.all(Radius.circular(10)),
                      ),
                      padding: EdgeInsets.all(7.0),
                      child: Text(closeAt),
                    ),
                  )
                ],
              ),
              s_height,
              Container(
                width: size.width * 0.8,
                child: ElevatedButton(
                  style: ButtonStyle(
                    foregroundColor: MaterialStateProperty.all<Color>(Styles.whiteColor),
                    backgroundColor: MaterialStateProperty.all<Color>(Styles.blackColor),
                  ),
                  onPressed: () => context.pop({"openAt": openAt, "closeAt": closeAt}),
                  child: Text("Save"),
                ),
              )
            ],
          ),
        ),
      ),
    );
  }
}

2. 调用模态框

test() {
  showModalBottomSheet<void>(
    isScrollControlled: true,
    isDismissible: false,
    enableDrag: false,
    barrierColor: Colors.black.withOpacity(0.1),
    backgroundColor: Colors.white,
    shape: const RoundedRectangleBorder(
      borderRadius: BorderRadius.only(
        topLeft: Radius.circular(15),
        topRight: Radius.circular(15),
      ),
    ),
    context: context,
    builder: (BuildContext context) => const TimePickerBottomSheet(),
  ).then((result) {
    // 接收保存的时间,更新上层组件状态
    if (result != null) {
      setState(() {
        // _openAt = result["openAt"];
        // _closeAt = result["closeAt"];
      });
    }
  });
}

补充细节
  • 处理showTimePicker返回null的情况(用户取消选择),避免空指针。
  • 不要直接使用value.toString(),默认格式为TimeOfDay(hh:mm),需手动格式化为hh:mm字符串。
  • 补充了Close at区域的时间选择逻辑(原代码未实现)。

内容的提问来源于stack exchange,提问作者Alexander Rogsberg

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 14:05:31