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

Flutter帧动画无延迟运行:解决帧切换闪白、延迟问题

解决Flutter帧动画闪白与帧切换延迟问题

闪白和延迟的核心原因是图片未预加载完成就启动动画,390张图片仅在initState中创建实例,但未等待资源加载完毕,导致帧切换时部分图片还在加载,显示空白背景;同时当前的绘制方式也存在性能浪费,加剧了延迟感。以下是针对性解决方案:

1. 强制预加载所有帧图片

在动画初始化前,等待所有图片加载并缓存到内存,确保切换时每帧资源都已就绪:

@override
void initState() {
  super.initState();
  SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft]);
  // 先预加载所有帧,再初始化动画
  _preloadAllFrames().then((_) {
    _initAnimation();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      _showStartDancingSnackbar();
    });
  });
}

// 预加载所有帧图片的方法
Future<void> _preloadAllFrames() async {
  final config = ImageConfiguration();
  for (int i = 1; i <= 390; i++) {
    final assetImage = AssetImage('assets/images/animationframes/ezgif-frame-$i.jpg');
    // 等待图片加载完成,确保缓存到内存
    await assetImage.resolve(config).completed;
    _spriteImages.add(Image(image: assetImage, fit: BoxFit.fill));
  }
}

// 单独抽离动画初始化逻辑
void _initAnimation() {
  _controller = AnimationController(
    duration: const Duration(seconds: 39),
    vsync: this,
    value: 0, // 初始设为第一帧
  );
  _animation = IntTween(begin: 0, end: _spriteImages.length - 1).animate(_controller);
}

2. 用RepaintBoundary减少不必要重绘

把动画区域包裹在RepaintBoundary中,避免帧切换时触发整个页面重绘,提升流畅度:

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: SafeArea(
      child: OrientationBuilder(builder: (context, orientation) {
        return LayoutBuilder(builder: (context, constraints) {
          final spriteWidth = MediaQuery.of(context).size.width;
          final spriteHeight = constraints.maxHeight;

          return GestureDetector(
            onTap: () async {
              if (_controller.isAnimating) {
                _controller.stop();
                setState(() => isPlaying = true);
                await audioPlayer.pause();
              } else {
                _controller.repeat();
                setState(() => isPlaying = false);
                await audioPlayer.resume();
              }
            },
            child: Center(
              // 添加RepaintBoundary隔离动画重绘区域
              child: RepaintBoundary(
                child: AnimatedBuilder(
                  animation: _animation,
                  builder: (context, child) {
                    return Stack(
                      alignment: Alignment.center,
                      children: [
                        Positioned.fill(
                          child: SizedBox(
                            height: spriteHeight,
                            width: spriteWidth,
                            child: _spriteImages[_animation.value],
                          ),
                        ),
                      ],
                    );
                  },
                ),
              ),
            ),
          );
        });
      }),
    ),
  );
}

3. 优化图片资源(推荐)

  • 压缩所有JPG图片,降低单张图片体积,减少加载耗时
  • 把390张帧图合并成精灵图(Sprite Sheet),通过CustomPaint绘制指定区域,大幅减少IO操作和内存占用,这是帧动画的最优解。示例思路:
// 假设已合并为一张sprite_sheet.png,按行列排列所有帧
final spriteSheet = Image.asset('assets/images/sprite_sheet.png');

// 在AnimatedBuilder中替换为CustomPaint
CustomPaint(
  painter: SpritePainter(
    spriteSheet: spriteSheet,
    currentFrame: _animation.value,
    totalFrames: 390,
    frameWidth: spriteWidth,
    frameHeight: spriteHeight,
  ),
  size: Size(spriteWidth, spriteHeight),
)

自定义SpritePainter实现帧的裁剪与绘制逻辑。

4. 修正动画控制器初始状态

原代码中_controller.repeat();后立即stop()属于无效操作,直接初始化为停止状态即可,避免不必要的性能开销(已整合到_initAnimation方法中)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 21:54:53