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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 01:39:32