ImageProvider工作流程和AssetImage 加载流程

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即会被绘制到屏幕上。

### 一、Flutter 中导入资源图片 在 Flutter 应用中,可以通过配置 `pubspec.yaml` 文件来导入资源图片。具体操作是在根目录下的 `assets` 配置项中指定资源路径[^1]: ```yaml flutter: uses-material-design: true assets: - images/ ``` 上述代码表示将 `images/` 文件夹中的所有图片作为应用的静态资源。 --- ### 二、使用资源图片 通过 `Image.asset` 小部件可以加载本地图片资源。例如,如果一张名为 `example.png` 的图片存放在 `images/` 文件夹中,则可以在界面中这样展示它[^2]: ```dart Image.asset('images/example.png', width: 100, height: 100,) ``` 此方法适用于任何存储于 `assets` 下的图片资源。 --- ### 三、保持资源同步 为了确保项目中新增加的图片能够被正确识别并编译到最终的应用包中,推荐使用工具如 **Flutter Img Sync** 来管理资源文件。该工具可以帮助开发者自动更新资源列表,并减少手动维护带来的错误风险[^3]。 运行命令如下: ```bash flutter pub run img_sync ``` 这一步骤尤其适合大型团队协作开发场景,能有效提升效率准确性。 --- ### 四、优化图片性能 当涉及大量高分辨率图像时,可能需要对其进行压缩以节省内存空间以及加快加载速度。为此可借助第三方库 `flutter_image_compress` 完成这一目标[^4]。以下是基本实现方式: #### 添加依赖 编辑 `pubspec.yaml` 文件加入插件支持: ```yaml dependencies: flutter: sdk: flutter flutter_image_compress: ^1.1.0 ``` #### 执行压缩 调用 API 对选定图片实施无损或者有损压缩处理: ```dart import 'package:flutter_image_compress/flutter_image_compress.dart'; Future<File> compressFile(File file) async { final result = await FlutterImageCompress.compressAndGetFile( file.absolute.path, '${file.parent}/compressed_${file.name}', quality: 88, ); return result!; } ``` 以上函数会返回一个新的经过压缩后的文件实例。 --- ### 五、增强用户体验——图片查看功能 针对某些应用场景比如相册浏览等需求,可以采用专门组件扩展交互能力。例如利用 `photo_view` 插件构建放大缩小效果良好的图片预览页面[^5]。 下面是一个简单的例子演示如何显示单张大图: ```dart @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text("Photo View Example")), body: Center( child: PhotoView( imageProvider: AssetImage("assets/large-image.jpg"), ), ), ); } ``` 而如果是多张连续翻页的情况则需要用到子类化版本即 `PhotoViewGallery` 提供更丰富的选项设置满足实际业务要求。 --- ### 总结 综上所述,在 Flutter 开发过程中合理规划资产结构并通过适当技术手段加以辅助可以使整个流程更加顺畅高效;同时注重细节方面的改进也能显著改善最终产品的质量表现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值