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

Flutter应用在FutureBuilder内添加动画后加载即冻结

问题原因

应用加载后直接冻结是几个逻辑错误叠加导致的:

  • FutureBuilder的future参数直接写在build方法里,传入了getMenuAndAccountInfo(pageLoader)的即时调用结果。只要组件触发重建(动画逐帧回调、setState调用、状态管理类更新都会触发重建),就会重新执行一遍异步拉取方法,请求完成后又会触发组件刷新,直接形成「重建→发请求→请求完成→重建」的无限死循环,占满UI线程导致应用无响应。
  • 动画触发逻辑顺序写反:按钮的onPressed回调里先调用空的setState(() {}),之后才修改splashOpacity = 1。setState执行时会立刻读取当前的splashOpacity值(还是初始的0)标记刷新,后续修改splashOpacity不会触发新的重建,AnimatedOpacity根本拿不到新的透明度值,动画完全无法触发。
  • 冗余代码问题:你混入了TickerProviderStateMixin,初始化了splashController和splashAnimation但全程没用到,也没写dispose释放逻辑,会造成内存泄漏;另外声明的width、tabProperty、menuData变量从未使用,徒增build方法执行开销。
修复步骤
  1. 把getMenuAndAccountInfo的调用提前到initState生命周期,存为状态变量再传给FutureBuilder,保证整个页面生命周期内异步请求只会执行一次,切断无限重建循环。
  2. 调整动画状态修改顺序:把splashOpacity的赋值放到setState的回调内部,确保框架重建时能读取到最新的透明度值,正常触发动画。
  3. 清理所有未使用的冗余代码,如果不需要手动控制动画,直接删掉没用的Animation相关声明即可;如果要保留自定义动画控制器,记得补全dispose释放逻辑。
  4. 给半透明遮罩层加IgnorePointer,避免遮罩显示时拦截下方页面的点击事件。
修复后可运行代码
class MainPageState extends State<MainPage> {
  GlobalKey imageKey = GlobalKey();
  double splashOpacity = 0;
  // 提前存储future实例,避免重复触发请求
  late Future<Map<String, dynamic>> pageLoadFuture;

  @override
  void initState() {
    super.initState();
    // 初始化时发起一次数据请求,整个页面生命周期只执行一次
    pageLoadFuture = getMenuAndAccountInfo(context.read<PageLoader>());
  }

  @override
  Widget build(BuildContext context) {
    double height = MediaQuery.of(context).size.height;
    PageLoader pageLoader = context.watch<PageLoader>();

    return WillPopScope(
      onWillPop: () async {
        bool login = false;
        await StorageManager.readData("loggedIn")
            .then((value) => login = value ?? false);
        if (login) {
          return false;
        }
        SystemNavigator.pop();
        return false;
      },
      child: FutureBuilder(
        future: pageLoadFuture,
        builder: ((context, snapshot) {
          // 数据加载中状态
          if (!snapshot.hasData) {
            return Stack(children: [
              SafeArea(
                child: Scaffold(
                  body: pageLoader.currentPage,
                ),
              ),
              const ColoredBox(
                color: Color.fromARGB(100, 0, 0, 0),
                child: Center(child: CircularProgressIndicator()),
              ),
            ]);
          }
          // 数据加载完成状态
          return SafeArea(
            child: RepaintBoundary(
              key: imageKey,
              child: Stack(
                children: [
                  Scaffold(
                    body: pageLoader.currentPage,
                    bottomNavigationBar: Container(
                      height: height * 0.07,
                      color: Colors.blue,
                      child: Row(
                        crossAxisAlignment: CrossAxisAlignment.end,
                        children: [
                          Expanded(
                            child: MaterialButton(
                                padding: EdgeInsets.zero,
                                splashColor: Colors.orange,
                                highlightColor: Colors.transparent,
                                onPressed: () {
                                  // 状态修改必须放在setState回调内
                                  setState(() {
                                    splashOpacity = 1;
                                  });
                                },
                                child: Column(
                                  mainAxisAlignment: MainAxisAlignment.center,
                                  children: [
                                    Icon(
                                      Icons.bookmark,
                                      color: Colors.white,
                                      size: height * 0.035,
                                    ),
                                    Text(
                                      "Bookmark",
                                      style: TextStyle(
                                          color: Colors.white,
                                          fontSize: height * 0.02),
                                    ),
                                  ],
                                )),
                          ),
                          Expanded(
                            child: MaterialButton(
                                padding: EdgeInsets.zero,
                                splashColor: Colors.orange,
                                highlightColor: Colors.transparent,
                                onPressed: () {
                                  setState(() {
                                    splashOpacity = 1;
                                  });
                                },
                                child: Column(
                                  mainAxisAlignment: MainAxisAlignment.center,
                                  children: [
                                    Icon(
                                      Icons.account_circle_rounded,
                                      color: Colors.white,
                                      size: height * 0.035,
                                    ),
                                    Text(
                                      "Profile",
                                      style: TextStyle(
                                          color: Colors.white,
                                          fontSize: height * 0.02),
                                    )
                                  ],
                                )),
                          ),
                        ],
                      ),
                    ),
                  ),
                  Positioned.fill(
                    // 加IgnorePointer避免遮罩层拦截下方组件点击事件
                    child: IgnorePointer(
                      child: AnimatedOpacity(
                        opacity: splashOpacity,
                        duration: const Duration(milliseconds: 400),
                        child: const ColoredBox(
                          color: Color.fromARGB(155, 0, 0, 0),
                        ),
                      ),
                    ),
                  )
                ],
              ),
            ),
          );
        }),
      ),
    );
  }
}

补充说明:你之前测试AlertDialog内无法正常调用setState,本质是因为AlertDialog属于独立路由上下文,调用setState时没有指定正确的状态作用域才会失效,自定义遮罩的方案完全可行,只要避开FutureBuilder重复执行的问题就不会出现卡死。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 10:15:30