• ImageProvider工作流程和AssetImage 加载流程


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

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

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

    1. Center(
    2. child: Container(
    3. width: width,
    4. height: height,
    5. decoration: BoxDecoration(
    6. borderRadius: BorderRadius.circular(radius ?? 0),
    7. image: DecorationImage(
    8. image: AssetImage(file!),
    9. fit: BoxFit.cover,
    10. )),
    11. ),
    12. )

    可以看到AssetImage作为DecorationImage属性。

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

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

    1. @override
    2. void paint(PaintingContext context, Offset offset) {
    3. assert(size.width != null);
    4. assert(size.height != null);
    5. _painter ??= _decoration.createBoxPainter(markNeedsPaint);
    6. ......
    7. }

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

       _painter ??= _decoration.createBoxPainter(markNeedsPaint);

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

    1. @factory
    2. BoxPainter createBoxPainter([ VoidCallback onChanged ]);

    最后实现在BoxDecoration类中

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

    直接看其paint方法

    1. @override
    2. void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
    3. assert(configuration != null);
    4. assert(configuration.size != null);
    5. final Rect rect = offset & configuration.size!;
    6. final TextDirection? textDirection = configuration.textDirection;
    7. _paintShadows(canvas, rect, textDirection);
    8. _paintBackgroundColor(canvas, rect, textDirection);
    9. _paintBackgroundImage(canvas, rect, configuration);
    10. _decoration.border?.paint(
    11. canvas,
    12. rect,
    13. shape: _decoration.shape,
    14. borderRadius: _decoration.borderRadius?.resolve(textDirection),
    15. textDirection: configuration.textDirection,
    16. );
    17. }

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

    1. void _paintBackgroundImage(Canvas canvas, Rect rect, ImageConfiguration configuration) {
    2. if (_decoration.image == null) {
    3. return;
    4. }
    5. _imagePainter ??= _decoration.image!.createPainter(onChanged!);
    6. Path? clipPath;
    7. switch (_decoration.shape) {
    8. case BoxShape.circle:
    9. assert(_decoration.borderRadius == null);
    10. final Offset center = rect.center;
    11. final double radius = rect.shortestSide / 2.0;
    12. final Rect square = Rect.fromCircle(center: center, radius: radius);
    13. clipPath = Path()..addOval(square);
    14. break;
    15. case BoxShape.rectangle:
    16. if (_decoration.borderRadius != null) {
    17. clipPath = Path()..addRRect(_decoration.borderRadius!.resolve(configuration.textDirection).toRRect(rect));
    18. }
    19. break;
    20. }
    21. _imagePainter!.paint(canvas, rect, clipPath, configuration);
    22. }

    上面主要功能:

    1.创建画笔

    2.在画布绘制图片

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

    1. /// Draw the image onto the given canvas.
    2. ///
    3. /// The image is drawn at the position and size given by the `rect` argument.
    4. ///
    5. /// The image is clipped to the given `clipPath`, if any.
    6. ///
    7. /// The `configuration` object is used to resolve the image (e.g. to pick
    8. /// resolution-specific assets), and to implement the
    9. /// [DecorationImage.matchTextDirection] feature.
    10. ///
    11. /// If the image needs to be painted again, e.g. because it is animated or
    12. /// because it had not yet been loaded the first time this method was called,
    13. /// then the `onChanged` callback passed to [DecorationImage.createPainter]
    14. /// will be called.
    15. void paint(Canvas canvas, Rect rect, Path? clipPath, ImageConfiguration configuration) {
    16. ......
    17. final ImageStream newImageStream = _details.image.resolve(configuration);
    18. if (newImageStream.key != _imageStream?.key) {
    19. final ImageStreamListener listener = ImageStreamListener(
    20. _handleImage,
    21. onError: _details.onError,
    22. );
    23. _imageStream?.removeListener(listener);
    24. _imageStream = newImageStream;
    25. _imageStream!.addListener(listener);
    26. }
    27. if (_image == null) {
    28. return;
    29. }
    30. if (clipPath != null) {
    31. canvas.save();
    32. canvas.clipPath(clipPath);
    33. }
    34. paintImage(
    35. canvas: canvas,
    36. rect: rect,
    37. image: _image!.image,
    38. debugImageLabel: _image!.debugLabel,
    39. scale: _details.scale * _image!.scale,
    40. colorFilter: _details.colorFilter,
    41. fit: _details.fit,
    42. alignment: _details.alignment.resolve(configuration.textDirection),
    43. centerSlice: _details.centerSlice,
    44. repeat: _details.repeat,
    45. flipHorizontally: flipHorizontally,
    46. opacity: _details.opacity,
    47. filterQuality: _details.filterQuality,
    48. invertColors: _details.invertColors,
    49. isAntiAlias: _details.isAntiAlias,
    50. );
    51. if (clipPath != null) {
    52. canvas.restore();
    53. }
    54. }

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

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

    1. final ImageStreamListener listener = ImageStreamListener(
    2. _handleImage,
    3. onError: _details.onError,
    4. );
    5. _imageStream?.removeListener(listener);
    6. _imageStream = newImageStream;
    7. _imageStream!.addListener(listener);

    图片加载后执行_handleImage方法

    1. void _handleImage(ImageInfo value, bool synchronousCall) {
    2. if (_image == value) {
    3. return;
    4. }
    5. if (_image != null && _image!.isCloneOf(value)) {
    6. value.dispose();
    7. return;
    8. }
    9. _image?.dispose();
    10. _image = value;
    11. assert(_onChanged != null);
    12. if (!synchronousCall) {
    13. _onChanged();
    14. }
    15. }

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

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

  • 相关阅读:
    SaaS CRM系统的优势,与本地部署相比哪个更方便?
    你应该知道的数仓安全:都是同名Schema惹的祸
    数据库期末考前复习题(单选+多选+判断+解答)
    SpringBoot+Vue项目在线小说阅读平台
    C#判断窗体是否被遮挡 - 开源研究系列文章
    Thinkphp5.x全漏洞复现分析
    Activity 的销毁流程
    spring源码解析——IOC-开启 bean 的加载
    深度解析 PyTorch Autograd:从原理到实践
    如何将ecology本地域名变成公网域名?
  • 原文地址:https://blog.csdn.net/bawomingtian123/article/details/128003706