圖片經常使用的格式主要有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
這是一個最基礎圖片容器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
這兩個屬性能夠作出許多的效果。動畫
看一段簡單的代碼
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模式下的樣子。
填充,忽略原有的寬高比,填滿爲止
包含,不改變原有比例讓容器包含整個圖片,容器多餘部分填充背景
覆蓋,不改變原有比例,讓圖片充滿整個容器,圖片多餘部分裁剪 ///
橫向圖片填充
縱向圖片填充
原始大小居中
圖片大小小於容器事至關於none,圖片大小大於容器時縮小圖片大小實現contain
centerSlice屬性專門用於nine-patch文件。
其餘屬性暫時不講。
通常狀況下這個控件不多使用,可是他是其餘Image控件的實現基礎,因此必需要拎出來說一下。
這是一個通用包裝類,它包裝了RawImage,同時提供了一些簡便的Named constructors
來使用AssetsImage,ExactAssetImage等ImageProvider的子類。
這個類的使用基本和RawImage一致,在使用的時候只是將參數ui.Image
包裝爲了ImageProvider
,不用再本身監聽ImageStream。典型簡單用法:
Widget image = Image(AssetImage("images/yuan.png"))
複製代碼
這個方法是ImageProvider
爲AssetImage
的簡單用法:
Widget image = Image.asset("images/yuan.png")
複製代碼
這個方法是ImageProvider
爲NetworkImage
的簡單用法:
Widget image = Image.network("http://img.rangaofei.cn/01b18.jpg")
複製代碼
這個方法是ImageProvider
爲FileImage
的簡單用法:
Widget image = Image.file(file)
複製代碼
這個方法是ImageProvider
爲MemoryImage
的簡單用法:
Widget image = Image.memory(byteList)
複製代碼
主要用來顯示用戶的頭像,任何圖片都會被剪切爲圓形。
一個簡單用法:
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
複製代碼
主要用於BoxDecoration
中的image屬性,能夠講圖片展現爲boxdecoration。這裏不作詳細解釋。
Widget getBoxImage() {
return Container(
decoration: BoxDecoration(
image: DecorationImage(image: AssetImage("images/yuan.png"))),
);
}
複製代碼
這個並非一個Widget,只能用在BoxDecoration
一樣很簡單,用於顯示一張圖片。 這裏至關於Ink一個簡單寫法,這個Ink控件裏邊只有decoration.image屬性的話,能夠直接替代Ink。
Widget getInkedImage() {
return Ink.image(image: AssetImage("images/timg.jpeg"));
}
複製代碼
基本和上邊的一致效果:
Widget getImageIcon() {
return ImageIcon(AssetImage("images/timg.jpeg"));
}
複製代碼
佔位圖漸變更畫控件,這個控件在加載網絡圖片時常常用到。主要做用是在加載網絡圖片這個耗時操做時顯示一個佔位圖,並在獲取到網絡圖片時以alpha動畫讓佔位圖淡出,網絡圖片淡入的效果。
看一下效果圖
可是這個並非一個銀彈,在加載單幀圖片時確實能夠達到很好的效果,可是在加載網絡動圖時會丟失顯示plcaceholder淡出時的全部幀,一樣在網絡圖片錯誤時並不會有錯誤的佔位圖,而是直接拋出異常。這裏不詳細解釋了。
上邊介紹了一些基本的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的數據格式:
咱們能夠經過給這個多幀解析器作一些手腳來達到控制gif動畫的目的。
這裏有我作的一個控件來控制gif的加載速度:
最後附上源碼:
初學者,還有我作的一個簡單的包: