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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值