前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Flutter动图加载机制解析

Flutter动图加载机制解析

作者头像
烧麦程
发布2022-05-10 20:49:33
1.3K0
发布2022-05-10 20:49:33
举报
文章被收录于专栏:半行代码

上文研究完 Flutter 的图片加载和缓存管理

Flutter图片加载和缓存机制探究

今天继续研究下 Flutter 是怎么处理动图的。Flutter 的 Image 加载默认会支持 gif、webp 等动态图片。在之前的文章中,我们会看到不同类型的图片加载逻辑是大致一样的,只是异步加载的逻辑不一样,

代码语言:javascript
复制
@override
ImageStreamCompleter load(
 final StreamController<ImageChunkEvent> chunkEvents =
        StreamController<ImageChunkEvent>();
  
  return MultiFrameImageStreamCompleter(
        chunkEvents: chunkEvents.stream,
        codec: _loadAsync(key as NetworkImage, decode, chunkEvents),
        scale: key.scale,
        debugLabel: key.url,
        informationCollector: _imageStreamInformationCollector(key));
}

load 方法都会返回一个 MultiFrameImageStreamCompleter 对象。从名字我们就有可以看到,这个可以处理多个帧的图片。这也是 Flutter 在加载图片的时候默认会使用的 Completer 对象。它的基类是 ImageStreamCompleter�

代码语言:javascript
复制
codec.then<void>(_handleCodecReady, onError: (dynamic error, StackTrace stack) {
      reportError(
        context: ErrorDescription('resolving an image codec'),
        exception: error,
        stack: stack,
        informationCollector: informationCollector,
        silent: true,
      );
});

图片加载完成后,会执行 _handleCodecReady 的逻辑。可以看到这里已经是开始图片解码了。

代码语言:javascript
复制
// _handleCodecReady
void _handleCodecReady(ui.Codec codec) {
    _codec = codec;
    assert(_codec != null);

    if (hasListeners) {
      _decodeNextFrameAndSchedule();
    }
}

这里会进行下一帧的解码:

代码语言:javascript
复制
// _handleCodecReady
void _handleCodecReady(ui.Codec codec) {
    _codec = codec;
    assert(_codec != null);

    if (hasListeners) {
      _decodeNextFrameAndSchedule();
    }
  }

// _decodeNextFrameAndSchedule
Future<void> _decodeNextFrameAndSchedule() async {
 try {
  // 获取下一帧
      _nextFrame = await _codec!.getNextFrame();
    } catch (exception, stack) {

  return;
 }
 if (_codec!.frameCount == 1) {
 // 只有一帧
 _emitFrame(ImageInfo(image: _nextFrame!.image, scale: _scale, debugLabel: debugLabel));
 return;
 }
 // 不止一帧,说明是动图
 _scheduleAppFrame();
}

void _scheduleAppFrame() {
 if (_frameCallbackScheduled) {
      return;
    }
    _frameCallbackScheduled = true;
    SchedulerBinding.instance!.scheduleFrameCallback(_handleAppFrame);
}

我们来梳理一下这里的逻辑:解析过程会尝试读取图片的下一帧。当图片解码信息里图片只有一帧的话,那么直接提交这一帧内容并结束, 如果 frameCount > 1 的话,则说明图片不止一帧内容,说明此时加载的是一张动图。继续执行 _sheduleAppFrame 方法。这个会在 ShedulerBinding 执行下一帧的时候执行 _handleAppFrame 方法。

代码语言:javascript
复制
void _handleAppFrame(Duration timestamp) {
 _frameCallbackScheduled = false;
 // 如果是第一帧
 if (_isFirstFrame() || _hasFrameDurationPassed(timestamp)) {
 
   // 提交一帧图片
   _emitFrame(ImageInfo(
    image: _nextFrame!.image, 
    scale: _scale, debugLabel: debugLabel));
   
   _shownTimestamp = timestamp;
   _frameDuration = _nextFrame!.duration;
   _nextFrame = null;
   // 计算这是第几次播放
   final int completedCycles = _framesEmitted ~/ _codec!.frameCount;


   // 如果重复次数是-1 或者完成的次数小于等于动图循环次数,继续执行_decodeNextFrameAndSchedule
   if (_codec!.repetitionCount == -1 || completedCycles <= _codec!.repetitionCount) {
        _decodeNextFrameAndSchedule();
      }
   return;
  
 }


 final Duration delay = _frameDuration! - (timestamp - _shownTimestamp);
    _timer = Timer(delay * timeDilation, () {
   // 循环到下一帧
      _scheduleAppFrame();
    });
}

这个方法里会处理当前的帧。如果是第一帧或者应该是下一帧出现的时间了,就会去提交该帧的内容。接下来会判断这张图是否播放完毕,如果没有,则会继续执行上面的解码工作,去解析下一帧图片。这里判断图片是否播放完毕依赖于两个条件:

  • 设置的重复次数是 -1,需要一直循环播
  • 播放的轮数小于设置的重复次数,轮数就是当前提交的帧数和图片总帧数取整。比如图片总共有10帧,播放到第32帧的时候,说明当前是第三轮。

整个动图的加载流程如图:

总结

从上面的代码中我们可以获取一些结论:

  • Flutter 默认是支持解析动图的,包括 webp、gif 这些
  • 我们可以自己参考上述内容去实现我们的动图播放,增加例如动画控制、动图播放监听等功能
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-11-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 半行代码 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档