Flutter中的Image入門講解

概覽

圖片經常使用的格式主要有bmp,jpg,png,gif,webp等。圖片也是一種二進制文件,每種格式的圖片都由固定的頭信息和真實數據塊組成。圖片原始數據每一個像素在內存中的佔用通常從2byte-4byte。c++

type bits memory
ARGB_8888 32 4*W*H
ARGB_4444 16 2*W*H
RGB_565 16 2*W*H
ALPHA_8 8 1*W*H

圖片加載到內存中的時候,假如是壓縮格式則會解壓縮爲RAW格式,並佔用上圖中memory中所佔用的內存。本文的一些內容會涉及到這些簡單的知識,有興趣的同窗能夠上網尋找更多資料。git

安卓開發者應該都知道Android中並非天生支持gif和webp動圖,可是這一特性在flutter中被很好的支持了。放一張官方的圖:github

flutter的圖像處理是在fluter engine中完成的,可是這個引擎提供的接口都是最基本的圖片信息,如何根據設定的屬性展現到屏幕上是在flutter中完成的。web

在engine中的./lib/ui/painting/codec.cc文件中展現了調用dart代碼的方法:canvas

static sk_sp<SkImage> DecodeImage(fml::WeakPtr<GrContext> context,
                                  sk_sp<SkData> buffer,
                                  size_t trace_id)
複製代碼

這個方法用於生成一個SkImage,並將主要屬性映射到flutter中的ui.Image類中。這個ui.Image就是能夠直接經過canvas渲染到屏幕上的數據。數組

常見控件

flutter提供了豐富的控件庫,可是咱們首先要搞清楚一個原理,全部的widget是不能直接繪製圖片的,而是做爲控制的圖片的主要屬性的容器,負責繪製的是RenderObject,他們中間經過ElementTree來聯繫起來。有了這個基礎後,全部的widget都不會提供畫布(canvas)來直接繪製image,因此在任何一個Widget源碼中都不會提供繪製的代碼。來看一下主要的Widget:bash

1. RawImage

這是一個最基礎圖片容器Widget。它能直接將原始圖片呈現到屏幕上,而且有一些列的屬性可供操做:網絡

const RawImage({
    Key key,
    this.image,
    this.width,
    this.height,
    this.scale = 1.0,
    this.color,
    this.colorBlendMode,
    this.fit,
    this.alignment = Alignment.center,
    this.repeat = ImageRepeat.noRepeat,
    this.centerSlice,
    this.matchTextDirection = false,
    this.invertColors = false,
    this.filterQuality = FilterQuality.low,
  }) : assert(scale != null),
       assert(alignment != null),
       assert(repeat != null),
       assert(matchTextDirection != null),
       super(key: key);
複製代碼

其中image類型就是上邊提到的ui.Image,這個數據的獲取官方推薦經過ImageStream添加listener來獲取。ide

  • color和colorBlendMode

這兩個屬性能夠作出許多的效果。動畫

看一段簡單的代碼

class _MyHomePageState extends State<MyHomePage> {
  ImageInfo info;//圖片信息
  List<BlendMode> blendModes = BlendMode.values;//全部的混合模式轉換爲list

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    Image.asset("images/yuan.png")
        .image
        .resolve(createLocalImageConfiguration(context))
        .addListener((ImageInfo image, bool synchronousCall) {
      setState(() {
        info = image; //刷新狀態
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GridView.builder(
        itemCount: blendModes.length - 1,
        padding: EdgeInsets.only(top: 10.0),
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 4,
        ),
        itemBuilder: getItemBuilder,
      ),
    );
  }

  Widget getItemBuilder(BuildContext context, int index) {
    return Column(
      children: <Widget>[
        RawImage(
          image: info?.image,
          color: Colors.red,
          width: 40,
          height: 40,
          colorBlendMode: blendModes[index + 1],
          fit: BoxFit.cover,
        ),
        Container(
          padding: EdgeInsets.only(top: 10.0),
          child: Text(
            blendModes[index + 1].toString().split("\.")[1],
            style: TextStyle(
              color: Colors.black,
              fontSize: 15.0,
            ),
          ),
        ),
      ],
    );
  }
}
複製代碼

flutter中的混合模式是枚舉類型,和Android中的圖片混合模式畫筆混合模式基本保持一致。上面的代碼描述了全部的混合模式並配有圖,除了clear沒有在裏邊(clear模式會清除全部內容)。image是一個簡單的圖片,帶透明通道的綠色的圓,在圖中就是dst模式下的樣子,背景是一個純紅色,在圖中就是src模式下的樣子。

  • fit屬性使用的是Boxfit的枚舉值,看一下效果:

  • fill

填充,忽略原有的寬高比,填滿爲止

  • contain

包含,不改變原有比例讓容器包含整個圖片,容器多餘部分填充背景

  • cover

覆蓋,不改變原有比例,讓圖片充滿整個容器,圖片多餘部分裁剪 ///

  • fitWidth

橫向圖片填充

  • fitHeight

縱向圖片填充

  • none

原始大小居中

  • scaleDown

圖片大小小於容器事至關於none,圖片大小大於容器時縮小圖片大小實現contain


centerSlice屬性專門用於nine-patch文件。

其餘屬性暫時不講。

通常狀況下這個控件不多使用,可是他是其餘Image控件的實現基礎,因此必需要拎出來說一下。

Image

這是一個通用包裝類,它包裝了RawImage,同時提供了一些簡便的Named constructors來使用AssetsImage,ExactAssetImage等ImageProvider的子類。

  • Image, 從ImageProvider來獲取圖片顯示

這個類的使用基本和RawImage一致,在使用的時候只是將參數ui.Image包裝爲了ImageProvider,不用再本身監聽ImageStream。典型簡單用法:

Widget image = Image(AssetImage("images/yuan.png"))
複製代碼
  • Image.asset, 從Asset資源中獲取圖片顯示

這個方法是ImageProviderAssetImage的簡單用法:

Widget image = Image.asset("images/yuan.png")
複製代碼
  • Image.network, 從URL獲取網絡圖片顯示

這個方法是ImageProviderNetworkImage的簡單用法:

Widget image = Image.network("http://img.rangaofei.cn/01b18.jpg")
複製代碼
  • Image.file, 從文件中獲取圖片顯示

這個方法是ImageProviderFileImage的簡單用法:

Widget image = Image.file(file)
複製代碼
  • Image.memory 從內存中獲取圖片顯示.

這個方法是ImageProviderMemoryImage的簡單用法:

Widget image = Image.memory(byteList)
複製代碼

CircleAvatar

主要用來顯示用戶的頭像,任何圖片都會被剪切爲圓形。

一個簡單用法:

CircleAvatar(
          child: Text("頭像"),
          backgroundImage: AssetImage("images/yuan.png"),
          backgroundColor: Colors.red,
          radius: 50.0,
        ),
複製代碼

生成的圖像以下:

CircleAvatar內置了許多的功能。radius用來控制圖片的大小,同時它能夠自動感知當前theme是白天模式仍是夜間模式來切換圖片顏色,另外它實際是包裝了AnimatedContainer,設置的動畫時間是200ms。在改變它的一些相關屬性時會自動使用動畫來執行。看一個簡單的動圖:

代碼以下:

class _MyHomePageState extends State<MyHomePage> {
  double radius = 10.0;

  @override
  void initState() {
    super.initState();
    Future<Duration>.delayed(Duration(milliseconds: 2 * 1000), () {
      setState(() {
        radius = 20.0;
      });
      return Duration(milliseconds: 210);
    }).then((Duration d) {
      Future<Duration>.delayed(d, () {
        setState(() {
          radius = 40.0;
        });
        return Duration(milliseconds: 210);
      }).then((Duration d) {
        Future<Duration>.delayed(d, () {
          setState(() {
            radius = 30.0;
          });
        });
      });
      ;
    });
  }

  @override
  Widget build(BuildContext context) {
   
    return Scaffold(
      body: Center(
        child: CircleAvatar(
          child: Text("頭像"),
          backgroundImage: AssetImage("images/yuan.png"),
          radius: radius,
        ),
      ),
    );
  }
}
複製代碼

這裏我並無使用AnimationController來控制radius值的變化,而是經過一個Future延時來控制。初始化的時候radius是10.0,延遲兩秒後變爲20.0,由於CircleAvatar默認的過分時間是200ms,爲了有一個平滑的過渡效果,我把下一次改變時間設置爲了210ms,這時半徑是40.0,最後通過210ms後半徑設置爲30.0,整個變化過程爲:

10->20->40->30
複製代碼

DecorationImage

主要用於BoxDecoration中的image屬性,能夠講圖片展現爲boxdecoration。這裏不作詳細解釋。

Widget getBoxImage() {
    return Container(
      decoration: BoxDecoration(
          image: DecorationImage(image: AssetImage("images/yuan.png"))),
    );
  }
複製代碼

這個並非一個Widget,只能用在BoxDecoration

Ink.image

一樣很簡單,用於顯示一張圖片。 這裏至關於Ink一個簡單寫法,這個Ink控件裏邊只有decoration.image屬性的話,能夠直接替代Ink。

Widget getInkedImage() {
    return Ink.image(image: AssetImage("images/timg.jpeg"));
  }
複製代碼

ImageIcon

基本和上邊的一致效果:

Widget getImageIcon() {
    return ImageIcon(AssetImage("images/timg.jpeg"));
  }
複製代碼

FadeInImage

佔位圖漸變更畫控件,這個控件在加載網絡圖片時常常用到。主要做用是在加載網絡圖片這個耗時操做時顯示一個佔位圖,並在獲取到網絡圖片時以alpha動畫讓佔位圖淡出,網絡圖片淡入的效果。

看一下效果圖

可是這個並非一個銀彈,在加載單幀圖片時確實能夠達到很好的效果,可是在加載網絡動圖時會丟失顯示plcaceholder淡出時的全部幀,一樣在網絡圖片錯誤時並不會有錯誤的佔位圖,而是直接拋出異常。這裏不詳細解釋了。

Image的加載過程

上邊介紹了一些基本的Image控件,這些都是屬於Widget層使用的,可是他們的主要依賴都是ImageProvider,咱們能夠經過訂製ImageProvider來實現本身的加載方式。

具體流程如圖

uint8list是dart中的一個高效的byte數組存儲類,用它來存儲圖片的二進制數據能夠更好的縮短轉換時間。

絕大多數圖片的加載過程都是將圖片資源轉換爲uint8list,處理uint8list爲ui.Image的過程是在Flutter engine中的skia中完成的,它不只包裝好了ImageInfo,同時包裝好了FrameInfo到Codec中,而後ImageStream經過ImageStreamCompleter來讀取Codec中的數據每一幀數據.ImageStreamCompleter是一個抽象類,共有兩個實現類,單幀解析器和多幀解析器,通常默認使用多幀解析器。

在解析出來的ImageInfo中能夠經過Future<ByteData> toByteData({ImageByteFormat format: ImageByteFormat.rawRgba})方法改變ImageByte的數據格式:

  1. rawRgba,未解碼的byte,每一個通道佔8bit
  2. rawUnmodified,未解碼且爲修改的byte,例如灰度圖
  3. png,最多見的無損數據格式

咱們能夠經過給這個多幀解析器作一些手腳來達到控制gif動畫的目的。

這裏有我作的一個控件來控制gif的加載速度:

最後附上源碼:

github.com/rangaofei/s…

初學者,還有我作的一個簡單的包:

pub.flutter-io.cn/packages/sa…

相關文章
相關標籤/搜索