在 Flutter 中實現最佳 UX 性能的 12 個圖像技巧和最佳實踐
Image widget 是 Flutter 中最常用的 widget 之一,但我相信我們沒有充分利用它的功能,僅僅顯示一個圖片是不夠的,你還應該給用戶他們需要的最佳體驗!
在這篇文章中,我將談論一些圖像技巧和最佳實踐,以獲得更好的性能和用戶體驗。
這些技巧是:
- 1.使用 WebP 而不是 JPG/PNG
- 2.設置寬度和高度以保留 UI 空間
- 3.降低圖片的顯示分辨率以減少內存使用
- 4.預加載/預緩存您的圖像,以便即時加載圖像
- 5.加載時顯示進度指示器
- 6.加載時顯示進度百分比指示器
- 7.加載時顯示閃爍效果,以提高用戶體驗
- 8.顯示 blurhash 作為占位符
- 9.使用漸變效果來提高用戶體驗
- 10.緩存圖像以減少網絡使用并提高性能
- 11.注意非經常性成本
- 12.在失敗時顯示重試按鈕
- 結語
1.使用 WebP 而不是 JPG/PNG
WebP 是下一代圖像格式,它比 PNG 和 JPEG 小約 25%,并且比其他格式快。
這意味著,你的應用程序將使用更少的內存,構建速度更快。
這里有一些基準:
圖片
圖片
Image.asset(
// 'image.jpg',
'image.webp', // PREFER
);
2.設置寬度和高度以保留 UI 空間
它可以防止應用程序出現布局偏移
圖片
之前——之后
Image.network(
imageUrl,
width: 200,
height: 150,
);
3.降低圖片的顯示分辨率以減少內存使用
圖片
圖片
你的圖片可能會導致設備內存膨脹,這是因為,盡管它們在 UI 中占據相對較小的一部分,Flutter 還是會以全分辨率渲染它們,從而消耗大量內存。
為了避免這種問題,可以使用 cacheWidth 或 cacheHeight 參數對指定大小的圖像進行解碼。
此外,我們可以使用 Flutter 開發者工具輕松檢測超大圖像。
如果圖像過大,它會反轉圖像,使其顛倒。
注意!緩存大小不應該小于小部件的大小,否則,由于分辨率低,它看起來像素化!
Image.network(
imageUrl,
cacheWidth: 100,
cacheHeight: 150,
);
4.預加載/預緩存您的圖像,以便即時加載圖像
如果你在顯示圖像之前緩存它們,Flutter 將跳過構建的處理步驟并立即顯示它們。
圖片
class MyImage extends StatefulWidget {
const MyImage({super.key});
@override
State createState() => _MyImageState();
}
class _MyImageState extends State {
late final Image myImage;
@override
void initState() {
super.initState();
myImage = Image.asset('path');
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
precacheImage(myImage.image, context);
}
@override
Widget build(BuildContext context) {
return myImage;
}
}
5.加載時顯示進度指示器
突然彈出圖像不是預期的行為,用戶可能會因為網絡連接不足而錯過圖像并向下滾動,或者可能在屏幕上看到一些空白,等等。我們應該始終通知用戶圖像正在加載。
圖片
return Image.network(
imageUrl,
loadingBuilder: (_, child, event) {
if (event == null) return child;
return const Center(child: CircularProgressIndicator());
},
);
6.加載時顯示進度百分比指示器
我們也可以顯示進度百分比,而不是無限加載,這樣對用戶來說更有用。
圖片
return Image.network(
imageUrl,
loadingBuilder: (_, child, event) {
if (event == null) return child;
return Center(
child: CircularProgressIndicator(
value: event.cumulativeBytesLoaded / (event.expectedTotalBytes ?? 0),
),
);
},
);
7.加載時顯示閃爍效果,以提高用戶體驗
顯示進度條是好的,但并不是最好的選擇,顯示閃爍效果(Shimmer)要比顯示進度條好得多。
圖片
return Image.network(
imageUrl,
height: 200,
width: 350,
loadingBuilder: (_, child, event) {
if (event == null) return child;
return const Shimmer(
height: 200,
width: 350,
);
}
);
// Most Basic Shimmer
class Shimmer extends StatefulWidget {
const Shimmer({
super.key,
this.width,
this.height,
this.minOpacity = 0.015,
this.maxOpacity = 0.15,
this.borderRadius = const BorderRadius.all(Radius.circular(4)),
this.child,
});
final double? width;
final double? height;
final double minOpacity;
final double maxOpacity;
final BorderRadius? borderRadius;
final Widget? child;
@override
State createState() => _ShimmerState();
}
class _ShimmerState extends State with SingleTickerProviderStateMixin {
late final AnimationController controller;
@override
void initState() {
super.initState();
controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 1),
lowerBound: widget.minOpacity,
upperBound: widget.maxOpacity,
)..repeat(reverse: true);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return RepaintBoundary(
child: FadeTransition(
opacity: controller,
child: Container(
width: widget.width,
height: widget.height,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: widget.borderRadius,
),
child: widget.child,
),
),
);
}
}
我創建了一個簡單的 shimmer 小部件,但你可以從官方文檔中學習如何創建高級版本的 shimmer 效果。
?
官方文檔:創建 shimmer 加載效果(https://docs.flutter.dev/cookbook/effects/shimmer-loading)
8.顯示 blurhash 作為占位符
為了改善用戶體驗,可以使用哈希代碼顯示圖像的模糊版本,而不是在圖像加載時顯示空白的灰色區域。
圖片
https://blurha.sh/
return const SizedBox(
width: 350,
height: 200,
child: BlurHash(
hash: hashCode,
imageFit: BoxFit.cover,
image: imageUrl,
),
);
9.使用漸變效果來提高用戶體驗
默認情況下,圖片加載后立即顯示,這對我們的視覺體驗來說非常糟糕,為了改善這一點,我們可以用一個小的漸入動畫來顯示它們。
我們可以使用 FadeInImage 來實現這個功能。
它需要字節或資源作為占位符,在這個例子中,我將使用 transparent_image 包來獲取透明圖像字節。
我們還可以使用 cached_network_image 包來實現這一點,以及更多。
return FadeInImage.memoryNetwork(
image: imageUrl,
placeholder: kTransparentImage,
);
// 或者
return CachedNetworkImage(
imageUrl: imageUrl,
);
10.緩存圖像以減少網絡使用并提高性能
為了避免每次下載相同的圖片,我們可以緩存第一次下載的圖片并重復使用,為了實現這一點,我們可以創建自己的緩存機制,或者我們可以直接使用 cached_network_image。
它緩存網絡圖像,默認情況下自動顯示它們的淡入效果,并提供了更多的圖像控制。
11.注意非經常性成本
Image widget 沒有 const 構造函數,雖然這在大多數情況下都不是問題,但我們可以通過將它包裝在自定義 widget 中來修復它。
它不僅可以讓我們的應用程序更具性能,而且我們還可以根據我們的意愿定制小部件,例如,我們可以為每個圖像小部件創建一個全局解決方案,而不是每次都處理 error/loading 情況。
enum _ImageType { asset, network }
class AppImage extends StatelessWidget {
const AppImage.asset(
this.image, {
super.key,
}) : type = _ImageType.asset;
const AppImage.network(
this.image, {
super.key,
}) : type = _ImageType.network;
final String image;
final _ImageType type;
@override
Widget build(BuildContext context) {
const errorWidget = Icon(Icons.error);
return switch (type) {
_ImageType.asset => Image.asset(
image,
errorBuilder: (_, __, ___) => errorWidget,
),
_ImageType.network => Image.network(
image,
errorBuilder: (_, __, ___) => errorWidget,
),
};
}
}
/// 使用
const AppImage.asset(''), // OK
const Image.asset(''), // 錯誤!!Image 沒有const構造函數
// 正如您所知,擁有 const 的小部件非常重要以獲得更好的性能。
12.在失敗時顯示重試按鈕
有時候由于網絡連接不好或其他原因,圖片無法第一次加載,顯示錯誤消息是好的,但這還不夠,如果我們想把應用的 UX 提升到另一個層次,我們應該讓用戶重新加載圖片,并繼續使用應用,而不會遇到任何麻煩。
圖片
class MyImage extends StatefulWidget {
const MyImage({super.key});
@override
State createState() => _MyImageState();
}
class _MyImageState extends State {
int attempt = 0;
@override
Widget build(BuildContext context) {
return CachedNetworkImage(
imageUrl: imageUrl,
cacheKey: '$attempt',
height: 200,
width: 250,
fit: BoxFit.cover,
errorWidget: (_, __, ___) {
return RetryWidget(
height: 200,
width: 250,
onTap: () => setState(() => attempt++),
);
},
);
}
}
// Just a basic retry button
class RetryWidget extends StatelessWidget {
const RetryWidget({
super.key,
required this.height,
required this.width,
required this.onTap,
});
final double? height;
final double? width;
final void Function() onTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
height: height,
width: width,
alignment: Alignment.center,
decoration: const BoxDecoration(color: Colors.black12),
child: const Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.image_not_supported, size: 20),
SizedBox(height: 12),
Padding(
padding: EdgeInsets.symmetric(horizontal: 12),
child: Text(
"Image couldn't load, tap here to retry",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 14, color: Colors.black),
),
),
],
),
),
);
}
}
結語
僅僅展示圖片是不夠的!您還應該為用戶提供他們需要的最佳體驗!因此,我強烈建議您創建自己的自定義圖片 widget,將它們隨意組合并自由使用!
原文:https://medium.com/itnext/12-image-tips-and-best-practices-for-the-best-ux-performance-in-flutter-e7a1b2b1da2a&strip=0&vwsrc=1&referer=medium-parser