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

Flutter中返回上一页时FadeInImage组件重复执行淡入动画的问题排查与解决

嘿,我来帮你搞定这个FadeInImage返回页面重复触发动画的问题!

问题出在哪?

你现在的ScreenOne是个StatelessWidget,每次从ScreenTwo返回时,Flutter都会重新构建整个ScreenOne组件——这就意味着里面的FadeInImage会被重新创建一遍,淡入动画自然就会重复执行。另外你尝试用AutomaticKeepAliveClientMixin没生效,是因为这个混入只能用在StatefulWidget的State类里,你的原组件是无状态的,所以完全起不了作用。

怎么解决?

我们把ScreenOne改成StatefulWidget,然后正确配置AutomaticKeepAliveClientMixin来维持组件状态,这样返回页面时就不会重新构建组件,动画也就不会重复触发了。

修改后的完整代码

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

  @override
  State<ScreenOne> createState() => _ScreenOneState();
}

class _ScreenOneState extends State<ScreenOne> with AutomaticKeepAliveClientMixin {
  // 告诉Flutter要保留这个组件的状态
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    // 这里必须调用super.build,不然AutomaticKeepAliveClientMixin不会生效,很多人容易漏这个!
    super.build(context);
    return GridView.builder(
        shrinkWrap: true,
        physics: const NeverScrollableScrollPhysics(),
        primary: false,
        padding: const EdgeInsets.only(left: 6, right: 6),
        itemCount: 4,
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          childAspectRatio: 180 / 304,
          crossAxisSpacing: 0,
          mainAxisSpacing: 0,
        ),
        itemBuilder: (BuildContext context, int index) {
          return InkWell(
              onTap: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) => const ScreenTwo()),
                );
              },
              child: Container(
                height: 191,
                width: 180,
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(9),
                ),
                child: Center(
                  child: ClipRRect(
                    borderRadius: BorderRadius.circular(9),
                    child: FadeInImage(
                      placeholder: const MemoryImage(kTransparentImage),
                      image: const NetworkImage('https://i.pravatar.cc/300'),
                      imageErrorBuilder: (BuildContext context, Object exception, StackTrace? stackTrace) {
                        return const Icon(Icons.error);
                      },
                      fit: BoxFit.contain,
                      height: 300,
                      width: 270,
                      // 可选优化:如果想让动画只在首次加载时触发,后续缓存加载直接显示,就加这行
                      // fadeInDuration: Duration.zero,
                    ),
                  ),
                ),
              ),
          );
        });
  }
}

class ScreenTwo extends StatelessWidget {
  const ScreenTwo({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Second Route"),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.pop(context);
          },
          child: const Text('Go back!'),
        ),
      ),
    );
  }
}

额外小技巧

如果你希望图片被缓存后,下次加载完全没有动画,可以给FadeInImage加上fadeInDuration: Duration.zero,这样只有第一次从网络加载时会有淡入效果,后续从缓存取图时直接显示。

再啰嗦两句为啥原来的方法没用

AutomaticKeepAliveClientMixin的作用是告诉Flutter“这个组件的状态要保留,别随便销毁”,但它只能和StatefulWidget搭配使用——你原来的ScreenOne是无状态组件,Flutter根本不会保存它的状态,所以不管怎么加mixin都白搭。改成有状态组件并正确实现mixin的两个要求(返回wantKeepAlive为true,调用super.build),问题就解决啦!

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 09:14:08