Flutter Photo View & Gallery – Resize & Rotate + Image Carousel

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;
      },
    );
  }
}
Don't forget to call dispose() on the PhotoViewController instance to release its resources and prevent memory leaks!

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.

Picture in a frame icon made by Eucalyp from www.flaticon.com is licensed by CC 3.0 BY
Matej Rešetár
 

Matej is an app developer with a knack for teaching others. If he's not programming, making tutorials or doing other business, he's mostly working out, listening to audiobooks and taking cold showers.

>