Skip to content

Commit

Permalink
GH #34: Initial work to add loading indicator
Browse files Browse the repository at this point in the history
This adds initial support for customizing the Image widget that is being displayed.
That includes customizing Image's `frameBuilder` and `loadingBuilder`. Rather
than providing those callbacks as parameters, it's more versatile to let the
user create their own Image widget using their desired configuration.

This is now supported in `EasyImageProvider` through a new `imageWidgetBuilder` method.
The default implementation shows a loading indicator and does a quick fade-in animation
once the image is loaded.

By using your own EasyImageProvider subclass this can be fully customized. Some tests
still fail and more tests need to be added, but this is a first shot at this.
  • Loading branch information
jfahrenkrug committed Dec 11, 2023
1 parent 7fc3a6d commit f0f884e
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 25 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ Show a bunch of images:

```dart
MultiImageProvider multiImageProvider = MultiImageProvider([
Image.network("https://picsum.photos/id/1001/5616/3744").image,
Image.network("https://picsum.photos/id/1003/1181/1772").image,
Image.network("https://picsum.photos/id/1004/5616/3744").image,
Image.network("https://picsum.photos/id/1005/5760/3840").image
const NetworkImage("https://picsum.photos/id/1001/4912/3264"),
const NetworkImage("https://picsum.photos/id/1003/1181/1772"),
const NetworkImage("https://picsum.photos/id/1004/4912/3264"),
const NetworkImage("https://picsum.photos/id/1005/4912/3264")
]);
showImageViewerPager(context, multiImageProvider, onPageChanged: (page) {
Expand Down
8 changes: 4 additions & 4 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ class _MyHomePageState extends State<MyHomePage> {
static const _kCurve = Curves.ease;

final List<ImageProvider> _imageProviders = [
Image.network("https://picsum.photos/id/1001/4912/3264").image,
Image.network("https://picsum.photos/id/1003/1181/1772").image,
Image.network("https://picsum.photos/id/1004/4912/3264").image,
Image.network("https://picsum.photos/id/1005/4912/3264").image
const NetworkImage("https://picsum.photos/id/1001/4912/3264"),
const NetworkImage("https://picsum.photos/id/1003/1181/1772"),
const NetworkImage("https://picsum.photos/id/1004/4912/3264"),
const NetworkImage("https://picsum.photos/id/1005/4912/3264")
];

late final _easyEmbeddedImageProvider = MultiImageProvider(_imageProviders);
Expand Down
32 changes: 32 additions & 0 deletions lib/src/easy_image_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,36 @@ abstract class EasyImageProvider {

/// Returns the image for the given [index].
ImageProvider imageBuilder(BuildContext context, int index);

/// Returns the image widget for the given [index].
Widget imageWidgetBuilder(BuildContext context, int index) {
return Image(
image: imageBuilder(context, index),
frameBuilder: (BuildContext context, Widget child, int? frame, bool wasSynchronouslyLoaded) {
if (wasSynchronouslyLoaded) {
return child;
}
return AnimatedOpacity(
opacity: frame == null ? 0 : 1,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
child: child,
);
},
loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) {
return IndexedStack(
index: loadingProgress == null ? 0 : 1,
alignment: Alignment.center,
children: <Widget>[
child,
CircularProgressIndicator(
value: loadingProgress?.expectedTotalBytes != null
? loadingProgress!.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes!
: null,
),
],
);
},
);
}
}
27 changes: 21 additions & 6 deletions lib/src/easy_image_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import 'package:flutter/material.dart';

/// A full-sized view that displays the given image, supporting pinch & zoom
class EasyImageView extends StatefulWidget {
/// The image to display
final ImageProvider imageProvider;
/// The image widget to display
final Widget imageWidget;

/// Minimum scale factor
final double minScale;
Expand All @@ -18,11 +18,27 @@ class EasyImageView extends StatefulWidget {
/// an interaction.
final void Function(double)? onScaleChanged;

/// Create a new instance that accepts an [ImageProvider]
EasyImageView.provider(ImageProvider imageProvider,
{Key? key,
double minScale = 1.0,
double maxScale = 5.0,
bool doubleTapZoomable = false,
void Function(double)? onScaleChanged})
: this(
key: key,
imageWidget: Image(image: imageProvider),
minScale: minScale,
maxScale: maxScale,
doubleTapZoomable: doubleTapZoomable,
onScaleChanged: onScaleChanged,
);

/// Create a new instance
/// The optional [doubleTapZoomable] boolean defaults to false and allows double tap to zoom.
const EasyImageView({
Key? key,
required this.imageProvider,
required this.imageWidget,
this.minScale = 1.0,
this.maxScale = 5.0,
this.doubleTapZoomable = false,
Expand Down Expand Up @@ -52,7 +68,6 @@ class _EasyImageViewState extends State<EasyImageView>

@override
Widget build(BuildContext context) {
final image = Image(image: widget.imageProvider);
return SizedBox.expand(
key: const Key('easy_image_sized_box'),
child: InteractiveViewer(
Expand All @@ -64,8 +79,8 @@ class _EasyImageViewState extends State<EasyImageView>
? GestureDetector(
onDoubleTapDown: _handleDoubleTapDown,
onDoubleTap: _handleDoubleTap,
child: image)
: image,
child: widget.imageWidget)
: widget.imageWidget,
onInteractionEnd: (scaleEndDetails) {
double scale = _transformationController.value.getMaxScaleOnAxis();

Expand Down
3 changes: 1 addition & 2 deletions lib/src/easy_image_view_pager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,9 @@ class _EasyImageViewPagerState extends State<EasyImageViewPager> {
controller: widget.pageController,
scrollBehavior: MouseEnabledScrollBehavior(),
itemBuilder: (context, index) {
final image = widget.easyImageProvider.imageBuilder(context, index);
return EasyImageView(
key: Key('easy_image_view_$index'),
imageProvider: image,
imageWidget: widget.easyImageProvider.imageWidgetBuilder(context, index),
doubleTapZoomable: widget.doubleTapZoomable,
onScaleChanged: (scale) {
if (widget.onScaleChanged != null) {
Expand Down
2 changes: 1 addition & 1 deletion test/easy_image_view_pager_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ void main() {
PageView pageView = tester.firstWidget(pageViewFinder);
expect(pageView.controller, pageController);
EasyImageView easyImageView = tester.firstWidget(easyImageViewFinder);
expect(easyImageView.imageProvider, imageProviders.first);
expect((easyImageView.imageWidget as Image).image, imageProviders.first);
});
});
}
16 changes: 8 additions & 8 deletions test/easy_image_view_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ void main() {

Widget testWidget = MediaQuery(
data: const MediaQueryData(size: Size(600, 800)),
child: EasyImageView(
imageProvider: imageProvider!, minScale: 0.5, maxScale: 6.0));
child: EasyImageView.provider(
imageProvider!, minScale: 0.5, maxScale: 6.0));

await tester.pumpWidget(testWidget);

Expand Down Expand Up @@ -56,8 +56,8 @@ void main() {

Widget testWidget = MediaQuery(
data: const MediaQueryData(size: Size(600, 800)),
child: EasyImageView(
imageProvider: imageProvider!,
child: EasyImageView.provider(
imageProvider!,
minScale: 0.5,
maxScale: 6.0,
onScaleChanged: (scale) {
Expand Down Expand Up @@ -105,8 +105,8 @@ void main() {

Widget testWidget = MediaQuery(
data: const MediaQueryData(size: Size(600, 800)),
child: EasyImageView(
imageProvider: imageProvider!,
child: EasyImageView.provider(
imageProvider!,
minScale: 0.5,
maxScale: 6.0,
doubleTapZoomable: true,
Expand Down Expand Up @@ -158,8 +158,8 @@ void main() {

Widget testWidget = MediaQuery(
data: const MediaQueryData(size: Size(600, 800)),
child: EasyImageView(
imageProvider: imageProvider!,
child: EasyImageView.provider(
imageProvider!,
minScale: 0.5,
maxScale: 6.0,
doubleTapZoomable: true,
Expand Down

0 comments on commit f0f884e

Please sign in to comment.