Flutter中的Image入門講解
圖片常用的格式主要有 bmp,jpg,png,gif,web
等。圖片也是一種二進位制檔案,每種格式的圖片都由固定的頭資訊和真實資料塊組成。圖片原始資料一般從2byte-4byte。
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 |
圖片載入到記憶體中的時候,假如是壓縮格式則會解壓縮,並佔用上圖中memory中所佔用的記憶體。本文的一些內容會涉及到這些簡單的知識,有興趣的同學可以上網尋找更多資料。
安卓開發者應該都知道Android中並不是天生支援gif和webp動圖,但是這一特性在flutter中被很好的支援了。放一張官方的圖:

flutter的影象處理是在engine中完成的,但是這個引擎提供的介面都是最基本的圖片資訊,如何根據設定的屬性展示到螢幕上是在flutter中完成的。
在engine中的 ./lib/ui/painting/codec.cc
檔案中展示了呼叫dart程式碼的資源:
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:
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來獲取。
-
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資源中獲取圖片顯示
這個方法是 ImageProvider
為 AssetImage
的簡單用法:
Widget image = Image.asset("images/yuan.png") 複製程式碼
- Image.network , 從URL獲取網路圖片顯示
這個方法是 ImageProvider
為 NetworkImage
的簡單用法:
Widget image = Image.network("http://img.rangaofei.cn/01b18.jpg") 複製程式碼
- Image.file , 從檔案中獲取圖片顯示
這個方法是 ImageProvider
為 FileImage
的簡單用法:
Widget image = Image.file(file) 複製程式碼
- Image.memory 從記憶體中獲取圖片顯示.
這個方法是 ImageProvider
為 MemoryImage
的簡單用法:
Widget image = Image.memory(byteList) 複製程式碼
CircleAvatar
主要用來顯示使用者的頭像,任何圖片都會被剪下為圓形。
一個簡單用法:
CircleAvatar( child: Text("頭像"), backgroundImage: AssetImage("images/yuan.png"), backgroundColor: Colors.red, radius: 50.0, ), 複製程式碼
生成的影象如下:

CircleAvatar
內建了許多的功能。randius用來控制圖片的大小,同時它可以自動感知當前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值的變化,而是通過一個延時來控制。初始化的時候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
來實現自己的載入方式。
具體流程如圖

在解析出來的ImageInfo中可以通過 Future<ByteData> toByteData({ImageByteFormat format: ImageByteFormat.rawRgba})
方法改變ImageByte的資料格式:
- rawRgba,未解碼的byte,每個通道佔8bit
- rawUnmodified,未解碼且為修改的byte,例如灰度圖
- png,最常見的無損資料格式
這裡有我做的一個控制元件來控制gif的載入速度:

最後附上原始碼:
ofollow,noindex">github.com/rangaofei/s…
初學者,還有我做的一個簡單的包: