ImageProvider工作流程和AssetImage 加载流程

news2024/11/16 19:50:30

Flutter 学习:ImageProvider工作流程和AssetImage 的自动分辨率适配原理https://cloud.tencent.com/developer/article/1748045上面流程为ImageProvider工作流程细节,作者已经写的很详细了,非常受用,现在接着上面作者内容讨论下AssetImage 加载图片数据后如何刷新。

我们知道加载图片肯定是异步的,不可能在一次刷新绘制就可以获取到图片的数据,只能是等待图片加载后再通知页面刷新,那么是如何通知页面刷新呢?

下面以一个AssetImage 加载为例进行说明。

Center(
      child: Container(
        width: width,
        height: height,
        decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(radius ?? 0),
            image: DecorationImage(
              image: AssetImage(file!),
              fit: BoxFit.cover,
            )),
      ),
    )

可以看到AssetImage作为DecorationImage属性。

在Flutter RenderObject Tree中,上面的Widget最终RenderObject Tree会包RenderDecoratedBox

这个就是装饰器的RenderObject,我们直接看源码

  @override
  void paint(PaintingContext context, Offset offset) {
    assert(size.width != null);
    assert(size.height != null);
    _painter ??= _decoration.createBoxPainter(markNeedsPaint);
    ......
    
  }

可以看到在paint方法里面创建_painter 时调用了createBoxPainer方法

   _painter ??= _decoration.createBoxPainter(markNeedsPaint);

这里我们记一下markNeedsPaint,后面会用到这个方法。

  @factory
  BoxPainter createBoxPainter([ VoidCallback onChanged ]);

最后实现在BoxDecoration类中

class BoxDecoration extends Decoration {
......
@override
BoxPainter createBoxPainter([ VoidCallback? onChanged ]) {
  assert(onChanged != null || image == null);
  return _BoxDecorationPainter(this, onChanged);
}
......
}
_BoxDecorationPainter类就是具体负责绘制装饰器的类,包含绘制阴影、背景和其他各种样式。

直接看其paint方法

  @override
  void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
    assert(configuration != null);
    assert(configuration.size != null);
    final Rect rect = offset & configuration.size!;
    final TextDirection? textDirection = configuration.textDirection;
    _paintShadows(canvas, rect, textDirection);
    _paintBackgroundColor(canvas, rect, textDirection);
    _paintBackgroundImage(canvas, rect, configuration);
    _decoration.border?.paint(
      canvas,
      rect,
      shape: _decoration.shape,
      borderRadius: _decoration.borderRadius?.resolve(textDirection),
      textDirection: configuration.textDirection,
    );
  }

由于我们这里是追踪图片的渲染流程,直接看 _paintBackgroundImage(canvas, rect, configuration);这个方法。

  void _paintBackgroundImage(Canvas canvas, Rect rect, ImageConfiguration configuration) {
    if (_decoration.image == null) {
      return;
    }
    _imagePainter ??= _decoration.image!.createPainter(onChanged!);
    Path? clipPath;
    switch (_decoration.shape) {
      case BoxShape.circle:
        assert(_decoration.borderRadius == null);
        final Offset center = rect.center;
        final double radius = rect.shortestSide / 2.0;
        final Rect square = Rect.fromCircle(center: center, radius: radius);
        clipPath = Path()..addOval(square);
        break;
      case BoxShape.rectangle:
        if (_decoration.borderRadius != null) {
          clipPath = Path()..addRRect(_decoration.borderRadius!.resolve(configuration.textDirection).toRRect(rect));
        }
        break;
    }
    _imagePainter!.paint(canvas, rect, clipPath, configuration);
  }

上面主要功能:

1.创建画笔

2.在画布绘制图片

同样的流程,我们继续查看_imagePainter!.paint() 方法,这个方法里面就是本文的重点。

  /// Draw the image onto the given canvas.
  ///
  /// The image is drawn at the position and size given by the `rect` argument.
  ///
  /// The image is clipped to the given `clipPath`, if any.
  ///
  /// The `configuration` object is used to resolve the image (e.g. to pick
  /// resolution-specific assets), and to implement the
  /// [DecorationImage.matchTextDirection] feature.
  ///
  /// If the image needs to be painted again, e.g. because it is animated or
  /// because it had not yet been loaded the first time this method was called,
  /// then the `onChanged` callback passed to [DecorationImage.createPainter]
  /// will be called.
  void paint(Canvas canvas, Rect rect, Path? clipPath, ImageConfiguration configuration) {
    ......
    final ImageStream newImageStream = _details.image.resolve(configuration);
    if (newImageStream.key != _imageStream?.key) {
      final ImageStreamListener listener = ImageStreamListener(
        _handleImage,
        onError: _details.onError,
      );
      _imageStream?.removeListener(listener);
      _imageStream = newImageStream;
      _imageStream!.addListener(listener);
    }
    if (_image == null) {
      return;
    }

    if (clipPath != null) {
      canvas.save();
      canvas.clipPath(clipPath);
    }

    paintImage(
      canvas: canvas,
      rect: rect,
      image: _image!.image,
      debugImageLabel: _image!.debugLabel,
      scale: _details.scale * _image!.scale,
      colorFilter: _details.colorFilter,
      fit: _details.fit,
      alignment: _details.alignment.resolve(configuration.textDirection),
      centerSlice: _details.centerSlice,
      repeat: _details.repeat,
      flipHorizontally: flipHorizontally,
      opacity: _details.opacity,
      filterQuality: _details.filterQuality,
      invertColors: _details.invertColors,
      isAntiAlias: _details.isAntiAlias,
    );

    if (clipPath != null) {
      canvas.restore();
    }
  }

该方法进来先注册回调,这个回调就是等待图片加载流完成后重新回调通知刷新,如果_image==null,也就是图片未加载完成,在直接return返回,等待回调执行,

如果_image!=null 在表明图片已经加载完成,则继续流程,运行到paintImage方法

     final ImageStreamListener listener = ImageStreamListener(
        _handleImage,
        onError: _details.onError,
      );
      _imageStream?.removeListener(listener);
      _imageStream = newImageStream;
      _imageStream!.addListener(listener);

图片加载后执行_handleImage方法

  void _handleImage(ImageInfo value, bool synchronousCall) {
    if (_image == value) {
      return;
    }
    if (_image != null && _image!.isCloneOf(value)) {
      value.dispose();
      return;
    }
    _image?.dispose();
    _image = value;
    assert(_onChanged != null);
    if (!synchronousCall) {
      _onChanged();
    }
  }

直接看这个方法最后一行,调用了_onChanged()方法,它是不是很熟悉,没错这个就是markNeedsPaint方法。

总结:在渲染图片时,由于加载图片内容耗时,我们注册一个markNeedsPaint回调方法,等待图片加载后,再调用Flutter 渲染流程里面的makeNeedsPaint方法,标记该RenderObject需要绘制,那么在下一次的绘制流中,该RenderObject即会被绘制到屏幕上。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/30420.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【32-业务开发-基础业务-规格参数-保存数据-查询数据-更新操作之数据回显展示-更新操作-前后端项目交互整合与测试-总结收获】

一.知识回顾 【0.三高商城系统的专题专栏都帮你整理好了,请点击这里!】 【1-系统架构演进过程】 【2-微服务系统架构需求】 【3-高性能、高并发、高可用的三高商城系统项目介绍】 【4-Linux云服务器上安装Docker】 【5-Docker安装部署MySQL和Redis服务】…

1532_AURIX_TriCore内核架构_中断

全部学习汇总: GreyZhang/g_tricore_architecture: some learning note about tricore architecture. (github.com) 中断一直是我想了解关注的一个功能模块,因为感觉不同的芯片上这部分的设计差异比较大。而这部分也跟我们嵌入式软件的设计模式直接相关。…

使用HikariCP连接池常用配置讲解及注意事项

使用HikariCP连接池常用配置讲解及注意事项常遇到的几种错误Possibly consider using a shorter maxLifetime valueConnection is not available, request timed out after xxxxxmsNo operations allowed after connection closed常见配置及注释说明,可以使用并根据…

每日刷题2——指针概念

更新不易,麻烦多多点赞,欢迎你的提问,感谢你的转发, 最后的最后,关注我,关注我,关注我,你会看到更多有趣的博客哦!!! 喵喵喵,你对我…

Twins: Revisiting the Design of Spatial Attention in Vision Transformers

Twins: Revisiting the Design of Spatial Attention in Vision Transformers一、引言二、Twins-PCPVT三、Twins-SVT四、实验五、消融实验文章链接: https://arxiv.org/abs/2104.13840代码链接: https://github.com/Meituan-AutoML/Twins一、引言 在本文中,重新审…

基于Matlab计算经典CFAR阈值

✅作者简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,matlab项目合作可私信。 🍎个人主页:Matlab科研工作室 🍊个人信条:格物致知。 更多Matlab仿真内容点击👇 智能优化算法 …

基于开源 PolarDB-X 打造中正智能身份认证业务数据基座

一、公司及业务介绍 中正智能是全球领先的生物识别和身份认证公司之一。我们曾负责公安部指纹算法国家标准的起草、编写,具备从算法、终端、平台、设计、生产、交付全域自研的能力,拥有多项自主知识产权的产品,并积极与高校合作开展基础研发。…

【Rust 指南】并发编程|无畏并发的原因

文章目录前言1、线程1.1、通过 spawn 创建新线程1.2、join 方法2、move 强制所有权迁移3、使用消息传递跨线程传递数据3.1、Send 方法3.2 、Sync 方法前言 安全高效的处理并发是 Rust 诞生的目的之一,主要解决的是服务器高负载承受能力。 并发(concurren…

石家庄正定县恢复种植 国稻种芯·中国水稻节:河北绘就画卷

石家庄正定县恢复种植 国稻种芯中国水稻节:河北绘就画卷 新华社记者 杨世尧 摄 河北日报 通讯员张 晓峰 摄影报道 新闻中国采编网 中国新闻采编网 谋定研究中国智库网 中国农民丰收节国际贸易促进会 国稻种芯中国水稻节 中国三农智库网-功能性农业农业大健康大会…

MongoDB备份与恢复

MongoDB备份与恢复 文章目录MongoDB备份与恢复1.备份恢复工具2.备份工具区别3.mongoexport导出工具命令4.导出MongoDB的表4.1.创建备份的目录4.2.登录数据库4.3.查询表和表中数据4.4.导出数据为json格式4.5.导出数据为csv格式5.mongoimport导入工具5.1.恢复json格式数据5.2.登录…

Python基础-3-列表

一:简述 列表是由一系列按特定顺序排列的元素组成,可以创建包含字母表中所有字母,数字或家庭成员姓名的列表;也可以将任何东西加入列表,其中的元素之间可以没有任何关系。列表中通常包含了多个元素,因此给…

HTTPS中间人攻击实验

HTTPS中间人攻击实验 一.实验基础 1、HTTPS概述 HTTPS (全称: Hyper Text Transfer Protocol over SecureSocketLayer), 是以安全为目标的HTTP通道,在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性。 默认端口:443 SSLspli…

【无人机】基于拓展卡尔曼滤波时序四旋翼无人机状态跟踪附matlab代码

✅作者简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,matlab项目合作可私信。 🍎个人主页:Matlab科研工作室 🍊个人信条:格物致知。 更多Matlab仿真内容点击👇 智能优化算法 …

SpringBoot+Vue项目实现疫情期间社区出入管理系统

文末获取源码 开发语言:Java 使用框架:spring boot 前端技术:JavaScript、Vue.js 、css3 开发工具:IDEA/MyEclipse/Eclipse、Visual Studio Code 数据库:MySQL 5.7/8.0 数据库管理工具:phpstudy/Navicat JD…

web前端期末大作业——基于Bootstrap响应式汽车经销商4S店官网21页

🎉精彩专栏推荐 💭文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 💂 作者主页: 【主页——🚀获取更多优质源码】 🎓 web前端期末大作业: 【📚毕设项目精品实战案例 (10…

【prometheus上报和使用】

prometheus上报和使用prometheus环境搭建进行上报CountrateirateincreaseGaugehistogram分位线summaryprometheus Prometheus是由SoundCloud开发的开源监控系统,由GO语言编写而成,采用Pull的方式来获取监控信息,并且提供了多维度的数据模型和…

Linux-sed

sed sed是一种几乎包括所有UNIX平台(包括Linux)的轻量级流编辑器。sed主要用来将数据进行选取、替换、删除、新增的命令 sed [选项] ‘[动作]’ 文件名 选项: -n:一般sed命令会把所有数据都输出到屏幕,如果加入此选项…

vue.js毕业设计,基于vue.js前后端分离订座预约系统设计与实现(H5移动项目)

功能介绍 【后台功能】 广告管理:设置小程序首页轮播图广告和链接 留言列表:所有用户留言信息列表,支持删除 会员列表:查看所有注册会员信息,支持删除 录入资讯:录入资讯标题、内容等信息 管理资讯&#x…

HTTP Only下的XSS攻击

今天继续给大家介绍渗透测试相关知识,本文主要内容是HTTP Only下的XSS攻击。 免责声明: 本文所介绍的内容仅做学习交流使用,严禁利用文中技术进行非法行为,否则造成一切严重后果自负! 再次强调:严禁对未授权…

程序猿成长之路之密码学篇-密码学简介

在阅读本文前需要了解的术语: 授权人/非授权人:授权人指获取了查看数据权限的用户,非授权人则是指未获取到权限的用户。明文/密文:明文指没有加密的数据内容,密文是指加密后的数据内容CIA(密码学中不是美国中情局的意思…