Flutter的 Image Widget

作者: summerlyy | 来源:发表于2018-11-23 11:01 被阅读65次

    源码:image.dart

    图片的显示

    class Image extends StatefulWidget

    Image 继承自 [StatefulWidget],它是具有状态的,通过

    @override///image.dart 574L 
    _ImageState createState() => _ImageState();
    

    可以找到 Image对应的State类是 _ImageState , 那么构建Widget的方法就在_ImageStatebuild方法中,如下:

      @override
      Widget build(BuildContext context) {
        final RawImage image = RawImage(
          image: _imageInfo?.image,
          width: widget.width,
          height: widget.height,
          scale: _imageInfo?.scale ?? 1.0,
          color: widget.color,
          colorBlendMode: widget.colorBlendMode,
          fit: widget.fit,
          alignment: widget.alignment,
          repeat: widget.repeat,
          centerSlice: widget.centerSlice,
          matchTextDirection: widget.matchTextDirection,
          invertColors: _invertColors,
          filterQuality: widget.filterQuality,
        );
        if (widget.excludeFromSemantics)
          return image;
        return Semantics(
          container: widget.semanticLabel != null,
          image: true,
          label: widget.semanticLabel == null ? '' : widget.semanticLabel,
          child: image,
        );
      }
    

    由源码可以看到,在此方法中创建的是 RawImage widget ,传入 imageInfo.image,并由 RawImage来渲染图片数据。

    图片的加载

    Image 类有这么几个构造方法,方便开发者加载显示本地,文件,网络中的图片数据。

    ///  * [new Image], for obtaining an image from an [ImageProvider].
    ///  * [new Image.asset], for obtaining an image from an [AssetBundle]
    ///    using a key.
    ///  * [new Image.network], for obtaining an image from a URL.
    ///  * [new Image.file], for obtaining an image from a [File].
    ///  * [new Image.memory], for obtaining an image from a [Uint8List].
    

    Image 加载了图片数据后存储在 imageInfo.imageImageInfo类很简单,只有两个属性:

    
    @immutable
    class ImageInfo {
      //图片像素点阵,
      final ui.Image image;
      //缩放比例,例当scale为2时,宽高都将变为原图的2倍。
      final double scale;
    }
    

    imageInfo又是在哪生成的呢,也就是在哪加载了图片的数据呢?根据 ImageInfo类的注释, ImageInfo 一旦被获得,就会被 ImageStream 用来表示为真实的图片数据。

    ImageStream的部分源码如下:

    class ImageStream extends Diagnosticable {
      /// Create an initially unbound image stream.
      ///
      /// Once an [ImageStreamCompleter] is available, call [setCompleter].
      ImageStream();
    
      /// The completer that has been assigned to this image stream.
      ///
      /// Generally there is no need to deal with the completer directly.
      ImageStreamCompleter get completer => _completer;
      ImageStreamCompleter _completer;
    
    

    可见 ImageStream 主要是由 ImageStreamCompleter 来提供支持,只是一个 ImageStreamCompleter的包装类,不过当ImageStreamCompleter可用的时候,需调用ImageStream.setCompleter方法,以将事件传递给ImageStream中的监听者。

    那么 ImageStreamCompleter又是个啥?继续往下看,源码:

    /// Base class for those that manage the loading of [dart:ui.Image] objects for
    /// [ImageStream]s.
    ///
    /// [ImageStreamListener] objects are rarely constructed directly. Generally, an
    /// [ImageProvider] subclass will return an [ImageStream] and automatically
    /// configure it with the right [ImageStreamCompleter] when possible.
    abstract class ImageStreamCompleter extends Diagnosticable {
      final List<_ImageListenerPair> _listeners = <_ImageListenerPair>[];
      ImageInfo _currentImage;
    
      void addListener(ImageListener listener, { ImageErrorListener onError }) {
          //省略
      }
    
      void removeListener(ImageListener listener) {
          //省略
      }
    
      /// Calls all the registered listeners to notify them of a new image.
      @protected
      void setImage(ImageInfo image) {
        _currentImage = image;
        if (_listeners.isEmpty)
          return;
        final List<ImageListener> localListeners = _listeners.map<ImageListener>(
          (_ImageListenerPair listenerPair) => listenerPair.listener
        ).toList();
        for (ImageListener listener in localListeners) {
          try {
            listener(image, false);
          } catch (exception, stack) {
            reportError(
              context: 'by an image listener',
              exception: exception,
              stack: stack,
            );
          }
        }
      }
    }
    

    ImageStreamCompleter是一个抽象类。去掉添加/移除Listener的方法后,还剩一个 [setImage] 方法,方法内部逻辑很简单,将传入的参数 ImageInfo 传递到各个 ImageListener,然后刷新GUI。

    ImageStreamCompleter有两个实现类,分别为

    • OneFrameImageStreamCompleter

    • MultiFrameImageStreamCompleter

    那刚才看下来的源码只是一个监听的设计而已:

    widget监听 ImageStream , 而widget设置给ImageStreamlistener 被传递到 ImageStreamCompleter。当图片成功加载时,ImageStreamCompletersetImage方法被调用,图片通过回调回传到 widget

    图片的加载2

    Image类构造方法需传入一个 ImageProvider,图片应便是在这里面被加载的:

    abstract class ImageProvider<T> {
    
      ///根据 configuration 处理 ImagePrivoder,并返回一个 ImageStream对象
      ///子类应该实现 [obtainKey] 和 [load] 方法,并且这两个方法在此流程中使用
      ImageStream resolve(ImageConfiguration configuration) {
        assert(configuration != null);
        final ImageStream stream = ImageStream();
        T obtainedKey;
        obtainKey(configuration).then<void>((T key) {
          obtainedKey = key;
          ///当拿到KEY时,查询缓存
          stream.setCompleter(PaintingBinding.instance.imageCache.putIfAbsent(key, () => load(key)));
        }).catchError(
            //省略错误处理...
            return null;
          }
        );
        return stream;
      }
    
      /// 根据 ImageConfiguration和 ImageProvider 的属性来生成一个KEY用来标识加载的图片
      /// KEY要求实现 '==' 和 hascode 方法,这个KEY主要是用于缓存
      @protected
      Future<T> obtainKey(ImageConfiguration configuration);
    
      ///开始加载图片
      @protected
      ImageStreamCompleter load(T key);
    }
    
    

    其中 resolve方法根据ImageConfiguration来获取相应的ImageStream

    到目前为止,图片获取的流程应该差不多可以这样来表示...

    image.png

    图片的缓存

    ImageProvider的源码中能过看到,图片的加载是做过缓存处理的。即在ImageProviderresolve方法中,有这么一句:

    stream.setCompleter(PaintingBinding.instance.imageCache.putIfAbsent(key, () => load(key)));

    1. KEY

    image.putIfAbsent传入的 key 的类型即为ImageProvider<T>中的T,需是不可改变的(immutable)以及实现[==] 和 [hashcode]方法。并且由 ImageProvider.obtainKey方法生成,例如NetworkImage中是这么实现的:

    class NetworkImage extends ImageProvider<NetworkImage> {
      @override
      Future<NetworkImage> obtainKey(ImageConfiguration configuration) {
        return SynchronousFuture<NetworkImage>(this);
      }
      @override
      bool operator ==(dynamic other) {
        if (other.runtimeType != runtimeType)
          return false;
        final NetworkImage typedOther = other;
        return url == typedOther.url
            && scale == typedOther.scale;
      }
    
      @override
      int get hashCode => hashValues(url, scale);
    }
    

    2.ImageCache

    缓存的逻辑主要在 putIfAbent方法中

    使用的 LRU,并且默认最多存储 1000个缓存,最大缓存限制为100MiB

    const int _kDefaultSize = 1000;
    const int _kDefaultSizeBytes = 100 << 20; // 100 MiB
    

    目前的图片大小是这么计算的:

    final int imageSize = info?.image == null ? 0 : info.image.height * info.image.width * 4;

    3.缓存

    由上代码可以看出,flutter 自带的缓存只会在运行期间生效,也就是缓存在内存中。

    相关文章

      网友评论

        本文标题:Flutter的 Image Widget

        本文链接:https://www.haomeiwen.com/subject/zmodqqtx.html