I'm using a FutureBuilder
to load album thumbnails into a ListTile
, as follows:
ListTile _albumTile(Album album, BuildContext context) {
return ListTile(
leading: FutureBuilder<Uint8List>(
future: AndroidContentResolver.instance.loadThumbnail(
uri: album.thumbnailUri,
width: 56,
height: 56,
),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Image.memory(snapshot.data!);
} else {
return const FittedBox(
fit: BoxFit.contain, child: Icon(Icons.album, size: 56));
}
}),
title: Text(album.album),
subtitle: Text(album.artist),
);
}
But, as the image loads, the placeholder icon is replaced with a blank image, and then with the final thumbnail. The blank image has the wrong width, which results in the ListTile
title and subtitle jumping around, causing a flicker.
The following sequence of screenshots shows three consecutive frames:
- Image placeholders.
- Some of the futures have completed, and are showing thumbnails. The other tiles have a blank image (and the text jumps to the left).
- All of the futures have completed.
Even when I fix the text jumping around -- by specifying fit: BoxFit.contain, width: 56, height: 56
for the image -- I still get a flash of white before the thumbnail appears.
What am I doing wrong?
CodePudding user response:
Use precacheImage function to prefetch an image into the image cache.
If the image is later used by an Image or BoxDecoration or FadeInImage, it will probably be loaded faster.
CodePudding user response:
I solved it by using frameBuilder
. The documentation says:
If this is null, this widget will display an image that is painted as soon as the first image frame is available (and will appear to "pop" in if it becomes available asynchronously).
This describes exactly what I'm seeing.
It continues with the following:
Callers might use this builder to add effects to the image (such as fading the image in when it becomes available) or to display a placeholder widget while the image is loading.
...and the attached example shows how to implement a fade-in effect. For simplicity, however, I opted for the following:
return Image.memory(
snapshot.data!,
frameBuilder: (context, child, frame, wsl) {
if (frame != null) {
return child;
} else {
return _albumThumbnailPlaceholder();
}
},
Widget _albumThumbnailPlaceholder() {
return FittedBox(fit: BoxFit.contain, child: Icon(Icons.album, size: 56));
}
This seems to completely get rid of the blank frame.