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

Instagram-style photo viewer with a jank-free Hero transition from a cover thumbnail grid: isomorphic Hero endpoints, deferred gesture layer, swipe-to-dismiss, pinch / double-tap zoom, paging.

flutter_hero_photo_viewer #

pub package pub points License: MIT Platform

English | 简体中文

Instagram-style photo viewer for Flutter: seamless, jank-free Hero transition from square cover thumbnail grids to a full-screen viewer with zero aspect-ratio distortion, zero flicker, swipe-to-dismiss, pinch/double-tap zoom, and paging.

All common Hero transition pitfalls are eliminated at the architectural level.

Features #

  • Isomorphic Hero Endpoints — Both ends use BoxFit.cover within matched geometric bounds (screenWidth × aspectRatio). The default Flutter RectTween interpolates cleanly without distortion.
  • Deferred Layering — Only the lightweight thumbnail is mounted during flight. High-resolution decoding and gesture handling layers attach only after the route animation completes.
  • Offstage Frame Guard — Guarded against Flutter's offstage layout frame where ModalRoute.animation falsely reports completed. Never displays destination layers prematurely.
  • Zero-Flicker Pop — Gesture and overlay layers dismount immediately on route reverse, preventing lingering full-size images during the flight back to the grid.
  • Physics Swipe-to-Dismiss — Smooth gesture handling (translation + scale + backdrop fade) without third-party page wrappers that cause flashes or coordinate jumps.
  • Multi-Touch Zoom & Paging — Pinch-to-zoom, double-tap zoom, and horizontal paging across multiple photos.
  • Decoupled Overlay System — Custom UI overlay (overlayBuilder) for title, pagination, actions, or close buttons, fading smoothly with dismiss gestures.
  • Zero Native Dependencies — Pure Dart/Flutter, fully compatible with iOS, Android, Web, macOS, Windows, and Linux.

Installation #

Add flutter_hero_photo_viewer to your pubspec.yaml:

dependencies:
  flutter_hero_photo_viewer: ^0.1.0

Or run:

flutter pub add flutter_hero_photo_viewer

Quick Start #

1. Grid Thumbnail #

Import the package:

import 'package:flutter_hero_photo_viewer/flutter_hero_photo_viewer.dart';

Wrap your grid item with HeroPhotoThumbnail:

HeroPhotoThumbnail(
  image: thumbProvider,
  heroTag: 'photo_$id',
  fit: BoxFit.cover,
)

Tip: You can also use your own widget, provided it wraps Hero(tag, flightShuttleBuilder: heroPhotoFlightShuttle) with a BoxFit.cover image and caches its aspect ratio via HeroPhotoSizes.remember.

2. Open Viewer #

HeroPhotoViewer.open(
  context,
  photos: [
    HeroPhoto(
      image: fullImageProvider,
      thumbnail: thumbProvider,
      heroTag: 'photo_$id',
    ),
  ],
  initialIndex: index,
);

Custom Overlay UI #

flutter_hero_photo_viewer handles flight, gestures, zoom, and paging. Any floating controls (close button, page indicator, share button, title, caption) are built using overlayBuilder:

// 1. Extend HeroPhoto to carry custom business metadata
class WorkPhoto extends HeroPhoto {
  const WorkPhoto({
    required super.image,
    super.thumbnail,
    super.heroTag,
    required this.title,
  });

  final String title;
}

// 2. Build custom overlay
HeroPhotoViewer.open(
  context,
  photos: works,
  initialIndex: i,
  overlayBuilder: (context, info) => HeroPhotoOverlayFade(
    progress: info.dismissProgress, // Fades out with swipe-to-dismiss
    child: Stack(
      children: [
        HeroPhotoCloseButton(onTap: info.close),
        HeroPhotoPageIndicator(index: info.index, total: info.total),
        Positioned(
          left: 0,
          right: 0,
          bottom: 80,
          child: Text(
            (info.photo as WorkPhoto).title,
            textAlign: TextAlign.center,
            style: const TextStyle(color: Colors.white, fontSize: 16),
          ),
        ),
        Positioned(
          right: 16,
          bottom: 40,
          child: IconButton(
            icon: const Icon(Icons.ios_share, color: Colors.white),
            onPressed: () => share(info.photo),
          ),
        ),
      ],
    ),
  ),
);

Pre-built Overlay Components #

  • HeroPhotoDefaultOverlay — Standard overlay with top close button and center page indicator.
  • HeroPhotoOverlayFade — Wraps your overlay and fades it out based on info.dismissProgress.
  • HeroPhotoCloseButton — Standard top-left circular close button.
  • HeroPhotoPageIndicator — Pill-style page index indicator (1 / 5).

Core Architecture: The Isomorphic Hero Principle #

Every transition bug in photo viewers stems from mismatched geometry or premature decoding. This package enforces 7 golden rules:

  1. Both endpoints are BoxFit.cover with the same ImageProvider.
    The full-screen container is not a full-screen box; it is a centered box of screenWidth × imageAspectRatio. When a cover image sits inside an aspect-fit box, it visually behaves like contain. The default RectTween handles the interpolation cleanly without stretching.
  2. Hero wraps only static images; flight uses only the thumbnail.
    Full-size images and gesture layers only mount after the route transition finishes (AnimationStatus.completed). Decoding high-res images on the push frame causes dropped frames.
  3. Route first frame is offstage; ModalRoute.animation reports false completed.
    During initial offstage layout, Flutter's route animation reports completed while the actual transition is still at 0.0. The package guards against this so the destination image never flickers prematurely.
  4. Interactive layer dismounts instantly on reverse.
    When the user pops or begins reverse navigation, the gesture layer is immediately unmounted so it does not linger or obscure the returning shuttle.
  5. Custom physics swipe-to-dismiss.
    Dismissal handles translation, scaling, and backdrop opacity natively without third-party sliding page packages that cause coordinate jumping.
  6. Pre-cached aspect ratios for zero-wait tap.
    HeroPhotoThumbnail records image aspect ratios in HeroPhotoSizes as soon as the grid renders. Clicking opens immediately without an asynchronous dimension query.
  7. enableLoadState: false on local image sources.
    Prevents infinite progress spinners on instant memory/file caches, ensuring widget tests and UI remain deterministic.

API Reference #

HeroPhotoViewer.open #

Parameter Type Default Description
context BuildContext required The navigation context.
photos List<HeroPhoto> required List of photos to display.
initialIndex int 0 Initial photo index to display.
backgroundColor Color Colors.black Background color of viewer.
enableSwipeToDismiss bool true Enable vertical swipe to dismiss.
overlayBuilder Widget Function(BuildContext, HeroPhotoViewerInfo)? null Custom top-level overlay builder.
callbacks HeroPhotoViewerCallbacks? null Callbacks for onOpen, onPageChanged, onDismiss.

HeroPhoto #

Property Type Description
image ImageProvider Full-resolution image provider.
thumbnail ImageProvider? Thumbnail provider matching grid cache (==).
heroTag Object? Hero tag matching grid thumbnail. null for fade-in only.
size Size? Image size/aspect ratio (defaults to HeroPhotoSizes.of(thumbnail)).

HeroPhotoViewerInfo #

Property Type Description
index int Current active page index (0-based).
total int Total number of photos.
photo HeroPhoto Current active photo object.
dismissProgress double Swipe-to-dismiss progress (0.0 normal, 1.0 dismissed).
settled bool True once Hero flight is completed.
close VoidCallback Programmatically triggers close animation.
pageController PageController Controller for the horizontal photo pager.

Testing #

The package includes comprehensive widget tests in test/hero_photo_viewer_test.dart asserting:

  • During flight, only the thumbnail layer is present at the destination.
  • High-res image and gesture layers only mount when completed.
  • First reverse/pop frame immediately dismounts the gesture layer.
  • Works gracefully without Hero tags (pure fade transition).

Run tests:

flutter test

License #

MIT License © 2026 Hugo

0
likes
150
points
52
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Instagram-style photo viewer with a jank-free Hero transition from a cover thumbnail grid: isomorphic Hero endpoints, deferred gesture layer, swipe-to-dismiss, pinch / double-tap zoom, paging.

Repository (GitHub)
View/report issues

Topics

#image-viewer #hero #photo-viewer #gallery #zoom

License

MIT (license)

Dependencies

extended_image, flutter, flutter_hooks

More

Packages that depend on flutter_hero_photo_viewer