image

27 Dec 2025

9K

35K

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:

  1. PhotoView: A powerful widget from the photo_view package designed to display a single image with pinch-to-zoom, pan, and rotation functionalities out of the box.
  2. PhotoViewGallery: Also from the photo_view package, this widget combines PageView with PhotoView, 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.builder is used to construct the gallery.
  • builder provides PhotoViewGalleryPageOptions for each image, specifying the imageProvider (e.g., NetworkImage, AssetImage, FileImage), and scale properties.
  • heroAttributes is used for smooth transitions if navigating to this screen using a Hero widget.
  • minScale, maxScale, and initialScale control the zoom levels.
  • loadingBuilder provides a visual indicator while images are loading.
  • _pageController manages the current page.
  • onPageChanged updates _currentIndex to 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.

Related Articles

May 14, 2026

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter Countdown timers are essential in many applic

May 11, 2026

Unleashing Dynamic UIs: Flutter's Animation Prowess

Unleashing Dynamic UIs: Flutter's Animation Prowess for Slide & Scale Effects Flutter's declarative UI framework, combined with its powerful animation capabilit

May 11, 2026

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy A well-designed Product Detail Page (PDP) is