需求:当App
的第一个页面是首页时,每次当App
启动时会立马显示网络图片,而不是需要等待Api
请求完成、等待图片下载后才显示图片,效果就相当于显示本地图片。
一、本地缓存Api
结果
去掉Api
请求的等待,是将Api
的请求结果缓存到本地,每次App
开启时优先从本地获取图片数据显示。
二、预加载图片到内存
去掉图片下载加载的等待,是在图片显示之前预加载图片到内存中,这样当图片显示的时候就不会感觉到有Loading
。
预加载需要结合cached_network_image
和precacheImage(ImageProvider provider, BuildContext context)
来完成,cached_network_image
负责将图片缓存到本地,precacheImage
负责将缓存图片预加载到内存。
三、预加载图片完成之后才显示首页
若App
的第一帧就是首页,我们需要保证预加载图片之后才显示首页,不然预加载图片到内存也是需要一定时间的,会导致图片有一段时间Loading
。
由于首页是第一帧,我们只能将预加载方法放在main()
方法里面,但预加载方法precacheImage
需要上下文context
,此时我们就需要用到deferFirstFrame()
和allowFirstFrame()
。
deferFirstFrame()
: 用于告诉Flutter
引擎暂时不要渲染首帧。在App
启动时,有一些初始化操作需要完成,例如数据预加载、用户身份验证等,防止UI
在未准备好时显示给用户,影响用户体验。allowFirstFrame()
:用于告诉Flutter
引擎可以渲染首帧了。在完成初始化任务后调用,需要与deferFirstFrame()
配套使用。
void main() {
// 延迟首帧
final binding = WidgetsFlutterBinding.ensureInitialized();
binding.deferFirstFrame();
runApp(MyApp());
// 模拟初始化操作
Future.delayed(Duration(seconds: 3), () {
binding.allowFirstFrame(); // 初始化完成后允许首帧渲染
});
}
四、显示图片
在显示图片的时候不要使用CachedNetworkImage
组件,因为CachedNetworkImage
提供了很多高级功能,例如占位图、加载指示器、错误显示等。其占位图是从加载图像时显示的,即时图片已经缓存到内存中了,占位图也会显示一小会儿。
使用CachedNetworkImageProvider
,主要用于更底层的图片加载逻辑,没有额外的UI
功能(如占位图、错误处理)。
五、参考代码
void main() {
final binding = WidgetsFlutterBinding.ensureInitialized();
binding
..deferFirstFrame()
..addPostFrameCallback((_) async {
final context = binding.rootElement;
if (context != null) {
await Init.instance.initialize(context);
}
binding.allowFirstFrame();
});
runApp(MyApp());
}
/// 初始化
class Init {
Init._();
static final instance = Init._();
final homeRepo = HomeHttpRepository();
Future<void> initialize(BuildContext context) async {
/// 預加載首頁輪播圖片
final homeBanners = await SpRepository.instance.getHomeBanners();
if (homeBanners != null) {
/// 本地有Api缓存数据,直接预加载
await precacheBannerImage(context, banners: homeBanners);
} else {
/// 本地没有Api缓存数据,先获取Api数据,再缓存
///
/// 注意:App一般会有欢迎页,所以若没有缓存数据,其实相当于第一次下载App,会先进入欢迎页,以下逻辑是在欢迎页完成,所以不需要等待
unawaited(
homeRepo.banners().then((value) {
precacheBannerImage(context, banners: value);
}),
);
}
}
/// 預加載輪播圖
Future<void> precacheBannerImage(
BuildContext context, {
required List<HomeBannerModel> banners,
}) async {
for (final banner in banners) {
if (banner.imageUrl.isURL) {
await precacheImage(
CachedNetworkImageProvider(banner.imageUrl!),
context,
);
}
}
}
}
/// 显示图片
Image(
fit: BoxFit.cover,
image: CachedNetworkImageProvider(imageUrl),
)