Display thumbnail while loading the actual image in the background
This commit is contained in:
parent
734807ba8d
commit
639c444a4e
4 changed files with 83 additions and 79 deletions
|
@ -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;
|
||||
}
|
||||
|
|
16
lib/core/thumbnail_cache.dart
Normal file
16
lib/core/thumbnail_cache.dart
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue