沒圖沒真相


在〈Assets 管理〉中,看過一些圖片的使用示範,如果你只是想載入某個來源的圖片,透過 Image.assetImage.networkImage.fileImage.memory 之類的建構式,是最方便的方式,分別可以從 asset、網路、檔案、記憶體中進行圖片載入。

ImageStatefulWidget 的子類,也就是它本身是個 Widget,可以安排在 Widget 樹的任何位置,建構 Image 時必要的引數其實是 ImageProvider

Image({Key key, @required ImageProvider image, ...)

ImageProvider 顧名思義,就是圖片的提供者,它是個抽象類別,中用過的 AssetImage 就是實作之一,其他還有 NetworkImageFileImageMemoryImage 等,Image.networkImage.fileImage.memory 就使用了這些類別的實例,建構 Image 時也可以自行指定 ImageProvider,這便於指定個別 ImageProvider 的細節,例如:

import 'package:flutter/material.dart';

void main() => runApp(
    MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text(
              'openhome.cc'
          ),
        ),
        body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Center(
              child: Image(
                image: NetworkImage('https://openhome.cc/Gossip/images/caterpillar.jpg', scale: 0.5),
                width: 200,
                height: 200,
                fit: BoxFit.none,
                color: Colors.red,
                colorBlendMode: BlendMode.colorBurn,
              ),
            ),
            Text('我是一隻弱小的毛毛蟲,想像有天可以成為強壯的挖土機,擁有挖掘夢想的神奇手套!')
          ],
        ),
      ),
    )
);

NetworkImage 的第一個參數是 src,也就是指定網路圖片來源,scale 是縮放比例,要注意的是,範例中的 widthheight 指定,並不是圖片本身的大小,而是圖片可繪製的區域大小。

嚴格說來,Image 只是個容器,不是圖片本身,也就是說 Image 只是個用來組合 ImageProviderRawImage 等資源的容器,Image 透過 ImageProvider 取得圖片資料,而真正繪製圖片的是 RawImagewidthheightfitcolorcolorBlendMode 等是提供給 RawImage 繪製時使用的資訊,實際上,Imagebuild 傳回的就是 RawImage

  ...
  @override
  Widget build(BuildContext context) {
    if (_lastException  != null) {
      assert(widget.errorBuilder != null);
      return widget.errorBuilder(context, _lastException, _lastStack);
    }

    Widget result = 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) {
      result = Semantics(
        container: widget.semanticLabel != null,
        image: true,
        label: widget.semanticLabel ?? '',
        child: result,
      );
    }

    if (widget.frameBuilder != null)
      result = widget.frameBuilder(context, result, _frameNumber, _wasSynchronouslyLoaded);

    if (widget.loadingBuilder != null)
      result = widget.loadingBuilder(context, result, _loadingProgress);

    return result;
  }

因此想知道 fitcolorcolorBlendMode 等的作用,要查詢的是 RawImage 的 API 文件,fit 是填滿方式,colorcolorBlendMode 表示指定的顏色與圖片要採用哪種混色演算,指定的顏色會逐像素地套用指定的演算法,來看一下執行結果:

有圖有真相

也就是說,Flutter 中 XXXImage 命名的元件,並不見得是 Image 的子類,通常是表示它包含了圖片資源罷了,實際上會如何呈現圖片或展現特效,要看它是怎麼組合相關資源的。

例如 FadeInImageStatelessWidget 的子類,只是 build 方法傳回 Image 實例,它可以指定大圖片載入前,先佔位顯示用的小圖,通常是在下載網路圖片時會用到,例如:

FadeInImage(
  placeholder: AssetImage(...),
  image: NetworkImage('https://openhome.cc/Gossip/images/caterpillar.jpg'),
)

另外還有 Ink.image,可以指定 ImageProvider,讓圖片在點選時,產生墨水渲染效果:

import 'package:flutter/material.dart';

void main() => runApp(
    MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text(
              'openhome.cc'
          ),
        ),
        body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Center(
              child: Ink.image(
                image: NetworkImage('https://openhome.cc/Gossip/images/caterpillar.jpg'),
                width: 200,
                height: 200,
                child: InkWell(
                  onTap: () {}, // 必須有 onTap 才會有墨水渲染效果
                )
              ),
            ),
            Text('我是一隻弱小的毛毛蟲,想像有天可以成為強壯的挖土機,擁有挖掘夢想的神奇手套!')
          ],
        ),
      ),
    )
);

只要認識 ImageImageProviderRawImage 之間基本的組合關係,再透過 API 文件,變化組合上應該就不難了,來看一下效果:

有圖有真相