需求:当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),
)



