Flutter?圖片開(kāi)發(fā)核心技能快速掌握教程
正文
在 Flutter 中使用圖片是最基礎(chǔ)能力之一。作為春節(jié)開(kāi)工后的第一篇文章,17 做了精心準(zhǔn)備,滿滿的都是干貨!本文
介紹如何在 Flutter 中使用圖片,盡量詳細(xì),示例完整,包會(huì)!
使用網(wǎng)絡(luò)圖片
使用網(wǎng)絡(luò)圖片超級(jí)簡(jiǎn)單,直接給出網(wǎng)絡(luò)地址就行,本例運(yùn)行后,顯示的是一張貓頭鷹的圖片。

完整代碼,貼到 main.dart 就能用。后面的代碼只給出 image 相關(guān)的。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
final imageSrc =
'https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/04ec6088c3c544a2b9459582e335483c~tplv-k3u1fbpfcp-watermark.image?';
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(child: Image.network(imageSrc)),
));
}
}
圖片加載成功后,不管 http 請(qǐng)求頭如何,都會(huì)被緩存起來(lái),下次請(qǐng)求這個(gè)圖片會(huì)直接從內(nèi)存中讀取。
一般我們需要指定圖片的寬度和高度,讓它以指定的尺寸顯示,避免圖片過(guò)大撐破布局。
Image.network(imageSrc,width: 100,height: 100,)
如果提供了 cacheWidth 或 cacheHeight,則指示引擎應(yīng)以指定大小解碼圖像。無(wú)論這些參數(shù)如何,圖像都將根據(jù)約束進(jìn)行渲染。cacheWidth 和 cacheHeight 主要是為了減少 ImageCache 的內(nèi)存使用。
cacheWidth 和 cacheHeight 是為了優(yōu)化內(nèi)存用的,如果你能確定網(wǎng)絡(luò)圖片的尺寸都是合適的尺寸,就不用設(shè)置這兩個(gè)參數(shù)。如果不能保證來(lái)源圖片的尺寸,比如可能有大尺寸的圖片,最好設(shè)置這兩個(gè)參數(shù)。這兩個(gè)參數(shù)只能優(yōu)化內(nèi)存占用,對(duì)下載和解碼沒(méi)有幫助。如果要優(yōu)化下載,需要把圖片緩存在磁盤(pán)上,下次直接從磁盤(pán)讀取,就像 web 緩存那樣。
把網(wǎng)絡(luò)圖片緩存到磁盤(pán)
我們可以用 cached_network_image 這個(gè)插件實(shí)現(xiàn)把網(wǎng)絡(luò)圖片緩存到磁盤(pán)這個(gè)功能。
安裝插件
flutter pub add cached_network_image
必須的參數(shù)只有一個(gè) imageUrl。
MaterialApp(
home: Scaffold(
body: Center(child: CachedNetworkImage(
imageUrl: imageSrc,
)),
));
cached_network_image 自帶 fadeIn 的效果,在圖片加載過(guò)程中顯示 placeholder,出現(xiàn)錯(cuò)誤,顯示 errorWidget。
CachedNetworkImage(
imageUrl: imageSrc,
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
)
有時(shí)我們需要把圖片應(yīng)用到其它 widget ,比如用在 BoxDecoration 中,這時(shí)需要提供 imageProvider。
CachedNetworkImage(
imageUrl: imageSrc,
imageBuilder: (context, imageProvider) => Container(
decoration: BoxDecoration(
image: DecorationImage(
image: imageProvider,
fit: BoxFit.cover,
colorFilter:
ColorFilter.mode(Colors.red, BlendMode.colorBurn)),
),
),
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
),
還有很多參數(shù),可以 在文檔中查看
使用 assets 圖片
assets 也可以叫做資源。資源是與您的應(yīng)用程序一起捆綁和部署的文件,可在運(yùn)行時(shí)訪問(wèn)。常見(jiàn)的資源類型包括靜態(tài)數(shù)據(jù)(例如 JSON 文件)、配置文件、圖標(biāo)和圖像(JPEG、WebP、GIF、動(dòng)畫(huà) WebP/GIF、PNG、BMP 和 WBMP)。
每個(gè)資源都由資源文件所在的顯式路徑(相對(duì)于 pubspec.yaml 文件)標(biāo)識(shí)。聲明資源的順序無(wú)關(guān)緊要。包含資源的目錄名稱無(wú)關(guān)緊要。
在構(gòu)建過(guò)程中,F(xiàn)lutter 將資源放入一個(gè)名為資源包的特殊存檔中,應(yīng)用程序會(huì)在運(yùn)行時(shí)從中讀取。
Flutter 使用位于項(xiàng)目根目錄的 pubspec.yaml 文件來(lái)識(shí)別應(yīng)用程序所需的資源。
資源文件夾的名稱是隨意的,我們可以把資源文件夾放在和 lib 平級(jí)的根目錄下面,為圖片建立文件夾 images,把上面示例中的貓頭鷹圖片放入其中。

修改 pubspec.yaml 的配置。
flutter:
assets:
- images/owl.png
注意空格
在代碼中可以通過(guò) images/owl.png 使用圖片。
Image.asset(
'images/owl.png',
width: 200,
height: 200,
);
運(yùn)行,成功顯示了貓頭鷹的圖片。當(dāng)你發(fā)布應(yīng)用程序的時(shí)候,pubspec.yaml 中配置的圖片會(huì)和代碼一起打包發(fā)布。
如果有很多圖片,這樣一張一張注冊(cè)很是麻煩,我們可以直接指定文件夾。比如我們可以一次性注冊(cè) images 文件夾下面的所有圖片。
flutter:
assets:
- images/
適配淺色與深色模式
正常情況下,我們用的是淺色模式,在弱光環(huán)境下,打開(kāi)深色模式可獲得出色的視覺(jué)體驗(yàn)。
構(gòu)建過(guò)程支持資源變體的概念:可以在不同上下文中顯示資源的不同版本。當(dāng)在 pubspec.yaml 中指定資源路徑時(shí),構(gòu)建過(guò)程會(huì)在相鄰子目錄中查找任何具有相同名稱的文件。然后,此類文件與指定資源一起包含在資源包中。
在 images 下面增加 dark 文件夾,增加在深色模式下使用的與淺色文件同名的圖片。17 的電腦屏幕截圖:

適配淺色與深色模式的工作就完成了!
images/owl.png 和 images/dark/owl.png 都包含在您的資源包中。前者被視為主要資源,而后者被視為變體。在淺色模式下,F(xiàn)lutter 為我們顯示顯示 images/owl.png ,在深色模式下顯示 images/dark/owl.png。
在不同的設(shè)備使用不同分辨率的圖片
Flutter 可以根據(jù)當(dāng)前設(shè)備像素比加載分辨率合適的圖像。
在 image 文件夾下增加 2.0x,3.0x文件夾,放入同名的高分辨率的圖片。17 的電腦屏幕的截圖:

1.5x 文件夾也是合法的。
適合不同設(shè)備分辨率的工作就完成了!
images/2.0x/owl.png 和 images/3.0x/owl.png 都包含在您的資源包中,都被視為變體。 flutter 會(huì)自動(dòng)為我們?cè)?dpr 為 2 的設(shè)備上使用 images/2.0x/owl.png 在 dpr 為 3 的設(shè)備上使用 images/3.0x/owl.png,在 dpr 為 1 設(shè)備上使用 images/owl.png。 images/owl.png 相當(dāng)于是 images/1.0x/owl.png。
dpr 為設(shè)備分辨率(device pixel ratio)英文單詞的首字母
還是一樣的代碼,現(xiàn)在可以適配不同 dpr 的設(shè)備!
Image.asset(
'images/owl.png',
width: 200,
height: 200,
);
關(guān)于設(shè)備 dpr 不完全匹配的處理
Flutter 以 dpr 2.0 為界,采用不同的處理方案,目的是為了得到更好的體驗(yàn)。
- 2.0 以下的設(shè)備匹配分辨率更高的圖片
- 2.0 以上的設(shè)備匹配分辨率最接近的圖片
比如有一個(gè) dpr 為 1.25 的設(shè)備,會(huì)采用 2.0 的圖片,而不是 1.0的圖片。再比如有一個(gè) dpr 為 2.25 的設(shè)備,會(huì)采用 2.0 的圖片,而不會(huì)采用 3.0 的圖片。
忽略 dpr 信息
如果要忽略 dpr 信息直接讀取主資源(就是不帶 x.0路徑的那個(gè)),用 ExactAssetImage。可以指定 scale,默認(rèn)為 1.0。
如果 scale 為 2.0,則意味著每個(gè)邏輯像素對(duì)應(yīng)四個(gè)圖像像素。看下實(shí)際的效果就明白了。

Column(
mainAxisSize: MainAxisSize.min,
children: [
Image(image: ExactAssetImage('images/owl.png', scale: 5)),
Image(image: ExactAssetImage('images/owl.png', scale: 10)),
],
)
scale 越大,圖片顯示的越小,因?yàn)?scale 越大,每個(gè)邏輯像素對(duì)應(yīng)的圖像像素就越多。
邏輯像素,也叫 設(shè)備獨(dú)立像素(device independent pixels),簡(jiǎn)稱 dip ,與具體設(shè)備無(wú)關(guān)。
使用相冊(cè)圖片
先安裝插件
flutter pub add image_picker
使用相冊(cè)圖片需要兩步
- 使用 image_picker 插件從相冊(cè)中讀取圖片
- 使用 Image.file 展示圖片
class MyWidget extends StatefulWidget {
const MyWidget({super.key});
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
final _picker = ImagePicker();
File? _file;
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () async {
var xfile = await _picker.pickImage(
source: ImageSource.gallery,
maxWidth: 200,
maxHeight: 300,
requestFullMetadata: false);
if (xfile != null) {
setState(() {
_file = File(xfile.path);
});
}
},
child: const Text('從相冊(cè)中選擇圖片')),
if (_file != null) Image.file(_file!)
],
);
}
}
maxWidth 和 maxHeight 最好設(shè)置一下,從源頭上控制一下圖片的大小,提高效率。如果這里沒(méi)控制大小,就必須讓 Image.file 加上 cacheWidth,cacheHeight參數(shù),因?yàn)橛?Image.file 顯示的圖片也會(huì)緩存起來(lái),需要控制緩存的圖片大小,減少內(nèi)存消耗。
requestFullMetadata: false 是為了避免 ios 閃退。requestFullMetadata: false 表示要請(qǐng)求完整的 metadata,需要在 info.plist( ios / Runner 下面 )中申請(qǐng)權(quán)限。
打開(kāi) info.plist 的 dict 中加入如下內(nèi)容就可以了。
<key>NSPhotoLibraryUsageDescription</key> <string>APP需要您的同意,才能使用相冊(cè),以便于上傳,發(fā)布照片</string>
android 不需要申請(qǐng)權(quán)限。
使用相機(jī)拍攝的圖片
和使用相冊(cè)圖片步驟一樣,有兩點(diǎn)不同
- source: ImageSource.gallery 修改為
source: ImageSource.camera - 申請(qǐng)相機(jī)的權(quán)限
android 不需要申請(qǐng)權(quán)限,直接可以使用相機(jī),ios 需要 打開(kāi) info.plist 在 dict 中加入如下內(nèi)容
<key>NSCameraUsageDescription</key> <string>APP需要您的同意,才能使用攝像頭,以便于相機(jī)拍攝,上傳、發(fā)布照片</string>
使用內(nèi)存圖片
Image.memory 的必選參數(shù) bytes 是 Uint8List 類型,base64Decode 的返回值正好是 Uint8List,我們用 Image.memory 展示一下 base64 格式的圖片。
我們得到的 base64格式的圖片可能是這樣的
imageString = 'image/jpeg;base64,/9j/4AA...'
把 image/jpeg;base64, 刪除,只保留后面的數(shù)據(jù),這樣才能正常顯示。
引用 dart:convert 把 imageString 用 base64Decode 轉(zhuǎn)成 Uint8List 類型,Image.memory 就能顯示了。
import 'dart:convert'; Image.memory(base64Decode(imageString));
圖片用做裝飾
DecoratedBox 是專門(mén)用來(lái)做裝飾的 widget
DecoratedBox(
decoration: BoxDecoration(
image: DecorationImage(image: AssetImage('images/owl.png')),
),
child: SizedBox(
width: 100,
height: 100,
),
)
更多時(shí)候,我們可以用 Container。
Container(
width: 100,
height: 100,
decoration: BoxDecoration(
image: DecorationImage(image: AssetImage('images/owl.png'))
),
)
DecorationImage 的 image 參數(shù)類型是 ImageProvider,ImageProvider 的子類都可以用作參數(shù)。除了 AssetImage,還可以用 FileImage,MemoryImage,NetworkImage。
圖片預(yù)加載
在網(wǎng)頁(yè)中的輪播圖中我們一般都會(huì)做圖片的預(yù)加載,用 js 預(yù)加載圖片,避免圖片在輪播時(shí)無(wú)法顯示。Flutter 中也有輪播圖,我們也可以做類似的事情。
和 js 預(yù)加載一樣,F(xiàn)lutter 預(yù)加載圖片也是很簡(jiǎn)單的。
preload(BuildContext context) {
var configuration = createLocalImageConfiguration(context);
for (var src in ['圖片地址1', '圖片地址2', '圖片地址13']) {
NetworkImage(src).resolve(configuration);
}
}
圖片什么時(shí)候加載完成不用管。Flutter 會(huì)用 NetWorkImage 做 key,緩存圖片,下次用 NetworkImage 加載同樣的圖片,無(wú)論是否加載完成,都不會(huì)再次加載。
resolve 的作用就是把加載的工作提前執(zhí)行。
判斷兩個(gè) NetWorkImage 相同,需要 url,scale 都相同,所以如果如果 scale 不同,會(huì)觸發(fā)重新加載。
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is NetworkImage
&& other.url == url
&& other.scale == scale;
}
centerSlice
centerSlice 用來(lái)切 9圖的。比如下面這張圖片,我們用 9 圖的方式來(lái)切圖。

這張圖是300 x 300 的圖,把它顯示成 400 x 400。我們把它分成 9 個(gè)區(qū)域。
- 區(qū)域 5 會(huì)被垂直水平拉伸
- 區(qū)域 4,6 被垂直拉伸
- 區(qū)域 2,8 被水平拉伸
- 1,3,7,9 保持原樣

Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset("images/btn.png",width: 300,height: 300,fit: BoxFit.fill,),
Image.asset("images/btn.png",
centerSlice: Rect.fromLTRB(100, 100, 200,200),width: 400,height:400,scale: 1,)
],
);
根據(jù) 9 圖的特性,我們可以把按鈕的背景圖做 9 圖,用來(lái)容納可變化的字?jǐn)?shù),也可以把聊天用的氣泡做成 9 圖。
centerSlice 只能放大,不能縮小。
原圖是 300 x 300, centerSlice 處理后的圖只能是寬比 300 大,高也比300 大,否則報(bào)錯(cuò)。
全局緩存 ImageCache 的設(shè)置
ImageCache 有兩個(gè)屬性
- maximumSize 可以獲取和設(shè)置可以緩存的圖片的最大數(shù)量,默認(rèn) 1000 張。
- maximumSizeBytes 可以獲取和設(shè)置可以緩存的圖片的最大容量,默認(rèn) 100M。
既然這兩個(gè)屬性可以讓我們?cè)O(shè)置,就說(shuō)明在有的時(shí)候,這兩個(gè)屬性的默認(rèn)值是不合適的。我們可以通過(guò) PaintingBinding.instance.imageCache 拿到全局 ImageCache 的實(shí)例,通過(guò)PaintingBinding.instance.imageCache.currentSize 監(jiān)控一下當(dāng)前已經(jīng)緩存的圖片數(shù)量,如果經(jīng)常達(dá)到最大值,說(shuō)明默認(rèn)值太小,可以設(shè)置的更大些。
maximumSize 設(shè)置的數(shù)量調(diào)高需要考慮到硬件的承受能力。如果硬件條件差,反而會(huì)適得其反。這個(gè)時(shí)候非但不能加大設(shè)置,還需要減小設(shè)置。
圖片類之間的關(guān)系
如果只是想會(huì)用 Flutter Image,上面的內(nèi)容就夠用了,可以跳過(guò)后面的內(nèi)容。
上面講了很多,都是零散的,涉及到的類很多,難免有混亂之感,所以需要對(duì)他們之間的關(guān)系梳理一下。
Image widget 是直接給我們用的。拋開(kāi)那些命名構(gòu)造函數(shù),如果我們直接用 Image,只有一個(gè)必須的參數(shù) image,image 類型是 ImageProvider。
ImageProvider
abstract class ImageProvider<T> {
ImageStream resolve(ImageConfiguration configuration) {
// 省略
}
Future<bool> evict({ ImageCache cache,
ImageConfiguration configuration = ImageConfiguration.empty }) async {
// 省略
}
Future<T> obtainKey(ImageConfiguration configuration);
@protected
ImageStreamCompleter load(T key); // 需子類實(shí)現(xiàn)
}
obtainKey(ImageConfiguration) 方法
該接口主要是為了配合實(shí)現(xiàn)圖片緩存,不同的 key 代表不同的圖片數(shù)據(jù)緩存。ImageProvider 從數(shù)據(jù)源加載完數(shù)據(jù)后,會(huì)在全局的 ImageCache 中緩存圖片。
resolve(ImageConfiguration) 方法
ImageStream resolve(ImageConfiguration configuration) {
... //省略
final ImageStream stream = ImageStream();
T obtainedKey; //
//定義錯(cuò)誤處理函數(shù)
Future<void> handleError(dynamic exception, StackTrace stack) async {
... //省略
stream.setCompleter(imageCompleter);
imageCompleter.setError(...);
}
// 創(chuàng)建一個(gè)新Zone,為了當(dāng)發(fā)生錯(cuò)誤時(shí)不會(huì)干擾 MainZone
final Zone dangerZone = Zone.current.fork(...);
dangerZone.runGuarded(() {
Future<T> key;
// 先驗(yàn)證是否已經(jīng)有緩存
try {
// 生成緩存key,后面會(huì)根據(jù)此key來(lái)檢測(cè)是否有緩存
key = obtainKey(configuration);
} catch (error, stackTrace) {
handleError(error, stackTrace);
return;
}
key.then<void>((T key) {
obtainedKey = key;
// 緩存的處理邏輯
final ImageStreamCompleter completer = PaintingBinding.instance
.imageCache.putIfAbsent(key, () => load(key), onError: handleError);
if (completer != null) {
stream.setCompleter(completer);
}
}).catchError(handleError);
});
return stream;
}
resolve 是 ImageProvider 對(duì)外暴露的主要方法,我們可以調(diào)用這個(gè)方法來(lái)加載圖片,Image Widget 也是調(diào)用這個(gè)方法加載圖片。
要從 ImageProvider 獲取 ImageStream,調(diào)用 resolve 并向其傳遞一個(gè) ImageConfiguration 對(duì)象。 ImageProvider 通過(guò) obtainKey 獲得 Key 并使用全局的 imageCache 緩存圖片。
類型參數(shù) T 是用于表示已解析配置的對(duì)象的類型。這也是圖像緩存中用于鍵的類型。它應(yīng)該是不可變的并實(shí)現(xiàn) == 運(yùn)算符和 hashCode getter。
AssetBundleImageProvider,F(xiàn)ileImage,MemoryImage,NetworkImage
這四個(gè)都是 ImageProvider 的子類,AssetBundleImageProvider 又有兩個(gè)子類,AssetImage 和 ExactAssetImage,這兩個(gè)和 FileImage,MemoryImage,NetworkImage 都可以直接給 image 參數(shù)賦值。
比如我們要讀取 owl.png
Image(image: AssetImage("image/owl.png"),);
Image.asset("image/owl.png");
這兩種都能顯示 owl.png。那么有什么區(qū)別呢?我們看下 Image.asset 構(gòu)造函數(shù)的源碼
Image.asset(
String name, {
省略...
}) : image = ResizeImage.resizeIfNeeded(
cacheWidth,
cacheHeight,
scale != null
? ExactAssetImage(name, bundle: bundle, scale: scale, package: package)
: AssetImage(name, bundle: bundle, package: package),
),
省略...
;
Image.asset 構(gòu)造函數(shù)為我們創(chuàng)建了 ImageProvider。
- 如果 scale 不為空,創(chuàng)建 ExactAssetImage,否則創(chuàng)建 AssetImage
- 用 ResizeImage.resizeIfNeeded 包起來(lái)。
ResizeImage.resizeIfNeeded 執(zhí)行下面的邏輯:如果 cacheWidth,cacheHeight 同時(shí)為空,直接返回原 ImageProvider,否則返回 ResizeImage。
static ImageProvider<Object> resizeIfNeeded(int? cacheWidth, int? cacheHeight, ImageProvider<Object> provider) {
if (cacheWidth != null || cacheHeight != null) {
return ResizeImage(provider, width: cacheWidth, height: cacheHeight);
}
return provider;
}
ResizeImage 在放進(jìn)緩存之前,會(huì)根據(jù) cacheWidth,cacheHeight 對(duì)圖片做優(yōu)化,這對(duì)于減少內(nèi)存開(kāi)銷有幫助。
從以上可以看出,沒(méi)有特殊需要,我們都使用 Image.asset、Image.network、Image.file、age.memory,這四個(gè)命名構(gòu)造函數(shù)。
ImageProvider 的子類還有一個(gè) ScrollAwareImageProvider, RawImage 會(huì)調(diào)用他避免在快速滾動(dòng)時(shí)加載圖像。我們一般不需要直接使用他。
以上就是Flutter 圖片開(kāi)發(fā)核心技能快速掌握教程的詳細(xì)內(nèi)容,更多關(guān)于Flutter 圖片開(kāi)發(fā)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Dart多態(tài)控制反轉(zhuǎn)編碼規(guī)范實(shí)例詳解
這篇文章主要為大家介紹了Dart多態(tài)控制反轉(zhuǎn)編碼規(guī)范實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11
Flutter SizedBox布局組件Widget使用示例詳解
這篇文章主要為大家介紹了Flutter SizedBox布局組件Widget使用示例詳解2023-02-02
Dart語(yǔ)法之變量聲明與數(shù)據(jù)類型實(shí)例詳解
這篇文章主要為大家介紹了Dart語(yǔ)法之變量聲明與數(shù)據(jù)類型實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
Flutter學(xué)習(xí)筆記(二)創(chuàng)建一個(gè)flutter項(xiàng)目
這篇文章主要介紹了Flutter學(xué)習(xí)筆記(二)創(chuàng)建一個(gè)flutter項(xiàng)目,其中運(yùn)行的過(guò)程,可能涉及到網(wǎng)絡(luò)環(huán)境配置的問(wèn)題,需要的朋友可以參考下2023-04-04
Flutter 語(yǔ)法進(jìn)階抽象類和接口本質(zhì)區(qū)別詳解
這篇文章主要為大家介紹了Flutter 語(yǔ)法進(jìn)階抽象類和接口本質(zhì)區(qū)別詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
一文詳解Dart如何實(shí)現(xiàn)多任務(wù)并行
這篇文章主要為大家介紹了Dart如何實(shí)現(xiàn)多任務(wù)并行示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03

