photo_zoom 0.1.0 copy "photo_zoom: ^0.1.0" to clipboard
photo_zoom: ^0.1.0 copied to clipboard

Pan, zoom and rotate images. Double tap and pinch zoom where you touch, mouse wheel zooms on desktop, and the gallery swipes between photos.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:photo_zoom/photo_zoom.dart';

void main() => runApp(const ExampleApp());

/// The photos the gallery shows. The aspect ratios differ on purpose, so the
/// difference between a contained and a covered scale is visible.
const photos = <Photo>[
  Photo('canyon', 'assets/canyon.png', Size(1200, 800), 'Canyon, 1200x800'),
  Photo('tower', 'assets/tower.png', Size(800, 1200), 'Tower, 800x1200'),
  Photo(
    'shoreline',
    'assets/shoreline.png',
    Size(1600, 900),
    'Shoreline, 1600x900',
  ),
  Photo('mosaic', 'assets/mosaic.png', Size(900, 900), 'Mosaic, 900x900'),
];

/// One photo in the gallery.
class Photo {
  const Photo(this.id, this.asset, this.size, this.label);

  final String id;
  final String asset;
  final Size size;
  final String label;

  ImageProvider get provider => AssetImage(asset);
}

class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key});

  @override
  Widget build(BuildContext context) => MaterialApp(
    title: 'photo_zoom',
    debugShowCheckedModeBanner: false,
    theme: ThemeData(colorSchemeSeed: Colors.indigo),
    darkTheme: ThemeData(
      colorSchemeSeed: Colors.indigo,
      brightness: Brightness.dark,
    ),
    home: const GridPage(),
  );
}

/// A grid of thumbnails that opens the gallery at the tapped photo.
class GridPage extends StatelessWidget {
  const GridPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('photo_zoom')),
      body: GridView.builder(
        padding: const EdgeInsets.all(8),
        gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
          maxCrossAxisExtent: 220,
          mainAxisSpacing: 8,
          crossAxisSpacing: 8,
        ),
        itemCount: photos.length,
        itemBuilder: (context, index) {
          final photo = photos[index];
          return GestureDetector(
            onTap: () => Navigator.of(context).push(
              MaterialPageRoute<void>(
                builder: (context) => GalleryPage(initialIndex: index),
              ),
            ),
            child: ClipRRect(
              borderRadius: BorderRadius.circular(12),
              // The same tag as the gallery page, so the thumbnail flies into it.
              child: Hero(
                tag: photo.id,
                child: Image(image: photo.provider, fit: BoxFit.cover),
              ),
            ),
          );
        },
      ),
    );
  }
}

/// The full screen gallery: swipe between photos, pinch or double tap to zoom.
class GalleryPage extends StatefulWidget {
  const GalleryPage({super.key, required this.initialIndex});

  final int initialIndex;

  @override
  State<GalleryPage> createState() => _GalleryPageState();
}

class _GalleryPageState extends State<GalleryPage> {
  late final PageController _pageController = PageController(
    initialPage: widget.initialIndex,
  );

  /// One controller per page, so each photo keeps its own zoom and the readout
  /// can follow whichever page is showing.
  late final List<PhotoViewController> _controllers = [
    for (final _ in photos) PhotoViewController(),
  ];

  late int _index = widget.initialIndex;

  @override
  void dispose() {
    _pageController.dispose();
    for (final controller in _controllers) {
      controller.dispose();
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final photo = photos[_index];
    return Scaffold(
      backgroundColor: Colors.black,
      extendBodyBehindAppBar: true,
      appBar: AppBar(
        backgroundColor: Colors.black38,
        foregroundColor: Colors.white,
        elevation: 0,
        title: Text(photo.label, style: const TextStyle(fontSize: 15)),
        actions: [
          IconButton(
            tooltip: 'Reset zoom',
            icon: const Icon(Icons.zoom_out_map),
            onPressed: () => _controllers[_index].reset(),
          ),
        ],
      ),
      body: Stack(
        children: [
          PhotoViewGallery.builder(
            pageController: _pageController,
            itemCount: photos.length,
            onPageChanged: (index) => setState(() => _index = index),
            backgroundDecoration: const BoxDecoration(color: Colors.black),
            builder: (context, index) {
              final photo = photos[index];
              return PhotoViewGalleryPageOptions(
                imageProvider: photo.provider,
                controller: _controllers[index],
                semanticLabel: photo.label,
                heroAttributes: PhotoViewHeroAttributes(tag: photo.id),
                minScale: PhotoViewComputedScale.contained * 0.8,
                maxScale: PhotoViewComputedScale.covered * 3,
              );
            },
            loadingBuilder: (context, event) => const Center(
              child: SizedBox(
                width: 24,
                height: 24,
                child: CircularProgressIndicator(strokeWidth: 2),
              ),
            ),
          ),
          Positioned(
            left: 0,
            right: 0,
            bottom: 24,
            child: Center(
              child: _ZoomReadout(controller: _controllers[_index]),
            ),
          ),
        ],
      ),
    );
  }
}

/// Follows a [PhotoViewController] and prints its scale, to show that the
/// controller is a plain [ValueNotifier] you can build against.
class _ZoomReadout extends StatelessWidget {
  const _ZoomReadout({required this.controller});

  final PhotoViewController controller;

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder<PhotoViewControllerValue>(
      valueListenable: controller,
      builder: (context, value, child) {
        final scale = value.scale;
        return DecoratedBox(
          decoration: BoxDecoration(
            color: Colors.black54,
            borderRadius: BorderRadius.circular(20),
          ),
          child: Padding(
            padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 7),
            child: Text(
              scale == null ? '--' : '${(scale * 100).toStringAsFixed(0)}%',
              style: const TextStyle(
                color: Colors.white,
                fontFeatures: [FontFeature.tabularFigures()],
              ),
            ),
          ),
        );
      },
    );
  }
}
0
likes
0
points
162
downloads

Publisher

verified publisherdeveloperyusuf.com

Weekly Downloads

Pan, zoom and rotate images. Double tap and pinch zoom where you touch, mouse wheel zooms on desktop, and the gallery swipes between photos.

Repository (GitHub)
View/report issues

Topics

#image #zoom #gallery #viewer #gestures

License

unknown (license)

Dependencies

flutter

More

Packages that depend on photo_zoom