Images speak louder than words. Having a good image is just half of the battle though. If the image is non-responsive, non-resizeable and, overall, the user experience is not all that great, your app will suffer
This is where Photo View library comes to the rescue! With it you can easily create images which can be resized and manipulated. On top of that, Photo View simplifies the creation of image carousels (galleries).
Setting up the project ?
The first step is to obviously import the library by updating the pubspec file.
pubspec.yaml
...
dependencies:
flutter:
sdk: flutter
photo_view: ^0.3.1
...
PhotoView basics
The default Image widget which comes with Flutter is perfect for simple images which need to be only displayed. PhotoView is here for you if the Image widget just doesn't cut it anymore.
Just like Image, PhotoView can work with Network, Asset, or any other kind of ImageProvider. In addition to that, users of the app can zoom in on an image and rotate it. The library also provides a convenient way to display a loading indicator while the image is being fetched.
What follows, is the most basic usage of a PhotoView which can be scaled across the whole screen.
simple_photo_view.dart
import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart';
class SimplePhotoViewPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Simple Photo View'),
),
body: PhotoView(
imageProvider: NetworkImage(
'https://resocoder.com/wp-content/uploads/2019/04/thumbnail-2.png',
),
// Contained = the smallest possible size to fit one dimension of the screen
minScale: PhotoViewComputedScale.contained * 0.8,
// Covered = the smallest possible size to fit the whole screen
maxScale: PhotoViewComputedScale.covered * 2,
enableRotation: true,
// Set the background color to the "classic white"
backgroundDecoration: BoxDecoration(
color: Theme.of(context).canvasColor,
),
loadingChild: Center(
child: CircularProgressIndicator(),
),
),
);
}
}
Inline PhotoView with a ClipRect
Zooming in on an image is surely a good user experience but what if you don't want the image to take up the whole screen? Constraining the size of the PhotoView widget can be done through other standard Flutter widgets like AspectRatio and ClipRect.
clipped_photo_view_page.dart
import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart';
class ClippedPhotoViewPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Clipped Photo View'),
),
body: Center(
// Dynamically set a fixed size for the child widget,
// so that it takes up the most possible screen space
// while adhering to the defined aspect ratio
child: AspectRatio(
aspectRatio: 16 / 9,
// Puts a "mask" on the child, so that it will keep its original, unzoomed size
// even while it's being zoomed in
child: ClipRect(
child: PhotoView(
imageProvider: NetworkImage(
'https://resocoder.com/wp-content/uploads/2019/04/thumbnail-2.png',
),
// Contained = the smallest possible size to fit one dimension of the screen
minScale: PhotoViewComputedScale.contained * 0.8,
// Covered = the smallest possible size to fit the whole screen
maxScale: PhotoViewComputedScale.covered * 2,
enableRotation: true,
loadingChild: Center(
child: CircularProgressIndicator(),
),
),
),
),
),
);
}
}
Advanced PhotoView controls
In addition to letting the user scale and rotate the PhotoView manually, you can also control it programatically by using a controller.
This controller also gives you access to the scale and rotation data of the PhotoView, so you can use them in some interesting way (maybe remember a certain scale & rotation for future use), or just display the obtained data on the screen as in this tutorial app.
controller_photo_view_page.dart
import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart';
class ControllerPhotoViewPage extends StatefulWidget {
@override
_ControllerPhotoViewPageState createState() =>
_ControllerPhotoViewPageState();
}
class _ControllerPhotoViewPageState extends State<ControllerPhotoViewPage> {
PhotoViewController photoViewController;
@override
void initState() {
super.initState();
photoViewController = PhotoViewController();
}
@override
void dispose() {
super.dispose();
//! Don't forget to dispose of the controller!
photoViewController.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Controller Photo View'),
),
// Stack puts widgets "on top" of each other
body: Stack(
children: <Widget>[
_buildPhotoView(context),
Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
_buildScaleInfo(),
_buildResetScaleButton(),
],
)
],
),
);
}
PhotoView _buildPhotoView(BuildContext context) {
return PhotoView(
controller: photoViewController,
imageProvider: NetworkImage(
'https://resocoder.com/wp-content/uploads/2019/04/thumbnail-2.png',
),
// Contained = the smallest possible size to fit one dimension of the screen
minScale: PhotoViewComputedScale.contained * 0.8,
// Covered = the smallest possible size to fit the whole screen
maxScale: PhotoViewComputedScale.covered * 2,
// Set the background color to the "classic white"
backgroundDecoration: BoxDecoration(
color: Theme.of(context).canvasColor,
),
loadingChild: Center(
child: CircularProgressIndicator(),
),
);
}
StreamBuilder<PhotoViewControllerValue> _buildScaleInfo() {
return StreamBuilder(
// Listening on the PhotoView's controller stream
stream: photoViewController.outputStateStream,
builder: (
BuildContext context,
AsyncSnapshot<PhotoViewControllerValue> snapshot,
) {
if (!snapshot.hasData) return Container();
return Center(
child: Text(
'Scale compared to the original: \n${snapshot.data.scale}',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 20),
),
);
},
);
}
RaisedButton _buildResetScaleButton() {
return RaisedButton(
child: Text('Reset Scale'),
onPressed: () {
photoViewController.scale = photoViewController.initial.scale;
},
);
}
}
Creating a photo gallery
In addition to PhotoView widget for single images, the Photo View library also provides a PhotoViewGallery. Its images can be specified either directly one by one (as ListView with direct children) or it can be populated with a builder (similar to ListView's builder).
In this tutorial, you're going to learn how to use the builder.
gallery_page.dart
import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart';
import 'package:photo_view/photo_view_gallery.dart';
class GalleryPage extends StatelessWidget {
final imageList = [
'https://resocoder.com/wp-content/uploads/2019/04/thumbnail-2.png',
'https://resocoder.com/wp-content/uploads/2019/04/thumbnail-1.png',
'https://resocoder.com/wp-content/uploads/2019/01/thumbnail.png',
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Gallery'),
),
// Implemented with a PageView, simpler than setting it up yourself
// You can either specify images directly or by using a builder as in this tutorial
body: PhotoViewGallery.builder(
itemCount: imageList.length,
builder: (context, index) {
return PhotoViewGalleryPageOptions(
imageProvider: NetworkImage(
imageList[index],
),
// Contained = the smallest possible size to fit one dimension of the screen
minScale: PhotoViewComputedScale.contained * 0.8,
// Covered = the smallest possible size to fit the whole screen
maxScale: PhotoViewComputedScale.covered * 2,
);
},
scrollPhysics: BouncingScrollPhysics(),
// Set the background color to the "classic white"
backgroundDecoration: BoxDecoration(
color: Theme.of(context).canvasColor,
),
loadingChild: Center(
child: CircularProgressIndicator(),
),
),
);
}
}
Conclusion
You've learned how to display very customizable, zoomable, pannable and rotatable images on Flutter with the Photo View library. The PhotoViewGallery is a good option for displaying zoomable image carousels.
Hello, I tried using only gallery_page.dart code from this blog. but I am getting following error. I am totally new to flutter.
Compiler message:
/C:/src/flutter/.pub-cache/hosted/pub.dartlang.org/photo_view-0.3.3/lib/photo_view.dart:380:24: Error: The argument type ‘Null Function(ImageInfo, bool)’ can’t be assigned to the parameter type ‘ImageStreamListener’.
– ‘ImageInfo’ is from ‘package:flutter/src/painting/image_stream.dart’ (‘/C:/src/flutter/packages/flutter/lib/src/painting/image_stream.dart’).
– ‘ImageStreamListener’ is from ‘package:flutter/src/painting/image_stream.dart’ (‘/C:/src/flutter/packages/flutter/lib/src/painting/image_stream.dart’).
Try changing the type of the parameter, or casting the argument to ‘ImageStreamListener’.
stream.addListener(listener);
^
/C:/src/flutter/.pub-cache/hosted/pub.dartlang.org/photo_view-0.3.3/lib/photo_view.dart:382:29: Error: The argument type ‘Null Function(ImageInfo, bool)’ can’t be assigned to the parameter type ‘ImageStreamListener’.