Display thumbnail while loading the actual image in the background

This commit is contained in:
Vishnu Mohandas 2020-04-25 02:17:21 +05:30
parent 734807ba8d
commit 639c444a4e
4 changed files with 83 additions and 79 deletions

View file

@ -1,5 +1,4 @@
import 'dart:collection';
import 'dart:typed_data';
typedef EvictionHandler<K, V>(K key, V value);
@ -34,33 +33,3 @@ class LRUMap<K, V> {
_map.remove(key);
}
}
class ImageLruCache {
static LRUMap<_ImageCacheEntity, Uint8List> _map = LRUMap(500);
static Uint8List getData(int id, [int size = 64]) {
return _map.get(_ImageCacheEntity(id, size));
}
static void setData(int id, int size, Uint8List imageData) {
_map.put(_ImageCacheEntity(id, size), imageData);
}
}
class _ImageCacheEntity {
int id;
int size;
_ImageCacheEntity(this.id, this.size);
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is _ImageCacheEntity &&
runtimeType == other.runtimeType &&
id == other.id &&
size == other.size;
@override
int get hashCode => id.hashCode ^ size.hashCode;
}

View file

@ -0,0 +1,16 @@
import 'dart:typed_data';
import 'package:myapp/core/lru_map.dart';
import 'package:myapp/models/photo.dart';
class ThumbnailLruCache {
static LRUMap<int, Uint8List> _map = LRUMap(500);
static Uint8List get(Photo photo) {
return _map.get(photo.generatedId);
}
static void put(Photo photo, Uint8List imageData) {
_map.put(photo.generatedId, imageData);
}
}

View file

@ -1,7 +1,7 @@
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:myapp/core/lru_map.dart';
import 'package:myapp/core/thumbnail_cache.dart';
import 'package:myapp/models/photo.dart';
import 'package:photo_manager/photo_manager.dart';
@ -25,9 +25,8 @@ class _ImageWidgetState extends State<ImageWidget> {
);
@override
Widget build(BuildContext context) {
final size = widget.size == null ? 124 : widget.size;
final cachedImageData =
ImageLruCache.getData(widget.photo.generatedId, size);
final size = widget.size == null ? 128 : widget.size;
final cachedImageData = ThumbnailLruCache.get(widget.photo);
Widget image;
@ -40,8 +39,7 @@ class _ImageWidgetState extends State<ImageWidget> {
.thumbDataWithSize(size, size),
builder: (context, snapshot) {
if (snapshot.hasData) {
ImageLruCache.setData(
widget.photo.generatedId, size, snapshot.data);
ThumbnailLruCache.put(widget.photo, snapshot.data);
Image image = _buildImage(snapshot.data, size);
return image;
} else {

View file

@ -1,70 +1,91 @@
import 'dart:typed_data';
import 'package:flutter/widgets.dart';
import 'package:logger/logger.dart';
import 'package:myapp/core/lru_map.dart';
import 'package:myapp/core/thumbnail_cache.dart';
import 'package:myapp/models/photo.dart';
import 'package:myapp/ui/image_widget.dart';
import 'package:myapp/ui/loading_widget.dart';
import 'package:photo_view/photo_view.dart';
class WidgetLruCache {
class ExpandedImageLruCache {
static LRUMap<int, Widget> _map = LRUMap(500);
static Widget get(Photo photo) {
static Widget getData(Photo photo) {
return _map.get(photo.generatedId);
}
static void put(Photo photo, Widget data) {
_map.put(photo.generatedId, data);
static void setData(Photo photo, Widget image) {
_map.put(photo.generatedId, image);
}
}
class ZoomableImage extends StatelessWidget {
class ZoomableImage extends StatefulWidget {
final Photo photo;
final Function(bool) shouldDisableScroll;
const ZoomableImage(
ZoomableImage(
this.photo, {
Key key,
this.shouldDisableScroll,
}) : super(key: key);
final Photo photo;
@override
_ZoomableImageState createState() => _ZoomableImageState();
}
class _ZoomableImageState extends State<ZoomableImage> {
ImageProvider _imageProvider;
bool _loadedThumbnail = false;
bool _loadedFinalImage = false;
ValueChanged<PhotoViewScaleState> _scaleStateChangedCallback;
@override
void initState() {
_scaleStateChangedCallback = (value) {
if (widget.shouldDisableScroll != null) {
widget.shouldDisableScroll(value != PhotoViewScaleState.initial);
}
};
super.initState();
}
@override
Widget build(BuildContext context) {
Logger().i("Building " + photo.toString());
if (WidgetLruCache.get(photo) != null) {
return WidgetLruCache.get(photo);
}
Logger().i("Cache miss " + photo.toString());
return FutureBuilder<Uint8List>(
future: photo.getBytes(),
builder: (_, snapshot) {
if (snapshot.hasData) {
final photoView = _buildPhotoView(snapshot.data);
WidgetLruCache.put(photo, photoView);
return photoView;
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
} else {
Logger().i("Loading");
return ImageWidget(photo);
}
},
);
}
Widget _buildPhotoView(Uint8List imageData) {
ValueChanged<PhotoViewScaleState> scaleStateChangedCallback = (value) {
if (shouldDisableScroll != null) {
shouldDisableScroll(value != PhotoViewScaleState.initial);
if (!_loadedThumbnail && !_loadedFinalImage) {
final cachedThumbnail = ThumbnailLruCache.get(widget.photo);
if (cachedThumbnail != null) {
_imageProvider = Image.memory(cachedThumbnail).image;
_loadedThumbnail = true;
}
};
return PhotoView(
imageProvider: Image.memory(imageData).image,
scaleStateChangedCallback: scaleStateChangedCallback,
minScale: PhotoViewComputedScale.contained,
);
}
if (!_loadedFinalImage) {
widget.photo.getBytes().then((bytes) {
if (mounted) {
setState(() {
final imageProvider = Image.memory(bytes).image;
precacheImage(imageProvider, context).then((value) {
if (mounted) {
setState(() {
_imageProvider = imageProvider;
_loadedFinalImage = true;
});
}
});
});
}
});
}
if (_imageProvider != null) {
final view = PhotoView(
imageProvider: _imageProvider,
scaleStateChangedCallback: _scaleStateChangedCallback,
minScale: PhotoViewComputedScale.contained,
gaplessPlayback: true,
);
return view;
} else {
return loadWidget;
}
}
}