Creating an Image Gallery Widget with Zoom in Flutter
Modern mobile applications often require displaying images in an interactive manner. A common feature is an image gallery that allows users to swipe through multiple images and zoom in on specific details. Flutter, with its rich set of widgets and robust ecosystem, provides excellent tools to build such a feature efficiently. This article will guide you through creating a professional image gallery widget with zoom functionality using Flutter.
Prerequisites
Before we begin, ensure you have the Flutter SDK installed and set up. We will also leverage the photo_view package, which greatly simplifies implementing zoom and pan capabilities for images. Add the following dependency to your pubspec.yaml file:
dependencies:
flutter:
sdk: flutter
photo_view: ^0.14.0 # Use the latest stable version
After adding the dependency, run flutter pub get in your terminal to fetch the package.
Core Components
The solution primarily relies on two key components:
PhotoView: A powerful widget from thephoto_viewpackage designed to display a single image with pinch-to-zoom, pan, and rotation functionalities out of the box.PhotoViewGallery: Also from thephoto_viewpackage, this widget combinesPageViewwithPhotoView, allowing you to create a scrollable gallery where each item is a zoomable image. It handles the integration seamlessly.
Step-by-Step Implementation
Let's walk through the process of building our image gallery widget.
1. Define Your Image Data
First, we need a list of image sources. For demonstration purposes, we'll use a list of network image URLs. In a real application, these could be asset paths, file paths, or more complex data structures.
final List<String> imageUrls = [
'https://images.unsplash.com/photo-1502675135487-e971002a6adb?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80',
'https://images.unsplash.com/photo-1547721064-cd27993a4034?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80',
'https://images.unsplash.com/photo-1518779578903-c5c8e317b960?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80',
'https://images.unsplash.com/photo-1557007554-15f21d607629?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80',
];
2. Create the Gallery Widget
We'll create a StatefulWidget called ImageGalleryScreen that manages the current image index and displays the PhotoViewGallery.
The PhotoViewGallery.builder constructor is ideal here as it efficiently builds each image page as it comes into view, similar to ListView.builder or PageView.builder.
import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart';
import 'package:photo_view/photo_view_gallery.dart';
class ImageGalleryScreen extends StatefulWidget {
final List<String> imageUrls;
final int initialIndex;
const ImageGalleryScreen({
Key? key,
required this.imageUrls,
this.initialIndex = 0,
}) : super(key: key);
@override
_ImageGalleryScreenState createState() => _ImageGalleryScreenState();
}
class _ImageGalleryScreenState extends State<ImageGalleryScreen> {
late PageController _pageController;
late int _currentIndex;
@override
void initState() {
super.initState();
_currentIndex = widget.initialIndex;
_pageController = PageController(initialPage: _currentIndex);
}
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
void _onPageChanged(int index) {
setState(() {
_currentIndex = index;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Image Gallery'),
backgroundColor: Colors.black,
),
body: Container(
decoration: const BoxDecoration(
color: Colors.black,
),
constraints: BoxConstraints.expand(
height: MediaQuery.of(context).size.height,
),
child: Stack(
children: [
PhotoViewGallery.builder(
scrollPhysics: const BouncingScrollPhysics(),
builder: (BuildContext context, int index) {
return PhotoViewGalleryPageOptions(
imageProvider: NetworkImage(widget.imageUrls[index]),
heroAttributes: PhotoViewHeroAttributes(tag: widget.imageUrls[index]),
minScale: PhotoViewComputedScale.contained * 0.8,
maxScale: PhotoViewComputedScale.covered * 4,
initialScale: PhotoViewComputedScale.contained,
basePosition: Alignment.center,
filterQuality: FilterQuality.high,
);
},
itemCount: widget.imageUrls.length,
loadingBuilder: (context, event) => Center(
child: SizedBox(
width: 20.0,
height: 20.0,
child: CircularProgressIndicator(
value: event == null
? 0
: event.cumulativeBytesLoaded / (event.expectedTotalBytes ?? 1),
),
),
),
backgroundDecoration: const BoxDecoration(
color: Colors.black,
),
pageController: _pageController,
onPageChanged: _onPageChanged,
),
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Text(
'${_currentIndex + 1} / ${widget.imageUrls.length}',
style: const TextStyle(
color: Colors.white,
fontSize: 18.0,
decoration: TextDecoration.none, // To remove default text decoration from Scaffold
),
),
),
),
],
),
),
);
}
}
In the above code:
- The
PhotoViewGallery.builderis used to construct the gallery. builderprovidesPhotoViewGalleryPageOptionsfor each image, specifying theimageProvider(e.g.,NetworkImage,AssetImage,FileImage), and scale properties.heroAttributesis used for smooth transitions if navigating to this screen using a Hero widget.minScale,maxScale, andinitialScalecontrol the zoom levels.loadingBuilderprovides a visual indicator while images are loading._pageControllermanages the current page.onPageChangedupdates_currentIndexto reflect the currently viewed image, which is then used to display a simple page indicator (e.g., "1 / 4").
3. Integrate into Your Application
Finally, you can integrate this ImageGalleryScreen into your main application. For instance, you might navigate to it when a user taps on an image thumbnail.
import 'package:flutter/material.dart';
import 'package:your_app_name/image_gallery_screen.dart'; // Adjust import path
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Image Gallery',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
final List<String> _imageUrls = const [
'https://images.unsplash.com/photo-1502675135487-e971002a6adb?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80',
'https://images.unsplash.com/photo-1547721064-cd27993a4034?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80',
'https://images.unsplash.com/photo-1518779578903-c5c8e317b960?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80',
'https://images.unsplash.com/photo-1557007554-15f21d607629?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80',
'https://images.unsplash.com/photo-1517616603091-a1829871131c?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80',
'https://images.unsplash.com/photo-1563721345680-e857417032ea?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80',
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Image Gallery Demo'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ImageGalleryScreen(
imageUrls: _imageUrls,
initialIndex: 2, // Start from the third image
),
),
);
},
child: const Text('Open Image Gallery'),
),
),
);
}
}
Conclusion
Creating a professional image gallery with zoom functionality in Flutter is straightforward, especially with the help of the photo_view package. By combining PhotoViewGallery with careful state management, you can provide users with an intuitive and responsive image browsing experience. This widget can be further enhanced with features like custom indicators, image captions, or integration with local storage for offline viewing, depending on your application's specific requirements.