2

It's a very common UX pattern that we are experiencing everyday:

Click an image in gallery, after a smooth transition, show the image in fullscreen mode and then you can zoom to view the details.

But in Flutter, I found it's hard to implement such animation smoothly, sure with Hero widget I can finish the basic code in a minute, but here is the problem: In the gallery, the image is placed in a small square with its fit mode BoxFit.cover, in zoom mode page, its fit mode should be BoxFit.contain. When hero animation starts of ends, the fit mode changed to target abruptly.

enter image description here

// main.dart
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show timeDilation;

const String url =
    'https://images.unsplash.com/photo-1635335333546-41f848cea96c';

void main() {
  runApp(
    const MaterialApp(
      home: GalleryWidget(),
    ),
  );
}

class GalleryWidget extends StatelessWidget {
  const GalleryWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    timeDilation = 10.0;
    return Scaffold(
      appBar: AppBar(
        title: const Text('Gallery'),
      ),
      body: Material(
        child: InkWell(
          child: Hero(
            tag: url,
            child: Image.network(
              url,
              width: 100.0,
              height: 100.0,
              fit: BoxFit.cover,
            ),
          ),
          onTap: () {
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => const PhotoViewerWidget(url: url),
                fullscreenDialog: true,
              ),
            );
          },
        ),
      ),
    );
  }
}

class PhotoViewerWidget extends StatelessWidget {
  final String url;

  const PhotoViewerWidget({Key? key, required this.url}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Material(
      color: Colors.black87,
      child: InkWell(
        child: Hero(
          tag: url,
          child: Image.network(url),
        ),
        onTap: () => Navigator.of(context).pop(),
      ),
    );
  }
}
Ryan Hoo
  • 360
  • 7
  • 19

2 Answers2

1

I'm willing to bet that the lagging animation is just because you're running the app in debug mode. While in debug mode there's a lot of background traffic that makes these transitions lag. Try running the app on a real device and when you do so run it from the terminal with this command flutter run --release. It'll install the app in release mode and you'll immediately notice the difference.

Let me know if that's the case.

Yasine Romdhane
  • 566
  • 4
  • 13
  • 3
    No, I didn't mean the lagging animation in debug mode. Check the gif above, you will notice a sudden change when the hero animation starts from full screen mode. The fullscreen image was in BoxFit.contain mode, suddenly it changed to BoxFit.cover, that makes the image height fill the screen before the hero animation, which leads to a bad user experience. – Ryan Hoo Nov 05 '21 at 07:13
0

the problem occurs due to different type of fit property in GallerWidget and PhotoViewerWidget. You can simply achieve to solution with using same fit property in both.

In your case :

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

class GalleryWidget extends StatelessWidget {
  GalleryWidget({Key? key}) : super(key: key);

  String url = 'https://images.unsplash.com/photo-1635335333546-41f848cea96c';

  @override
  Widget build(BuildContext context) {
    timeDilation = 10.0;
    return Scaffold(
      appBar: AppBar(
        title: const Text('Gallery'),
      ),
      body: Material(
        child: InkWell(
          child: Hero(
            tag: url,
            child: SizedBox(
              width: 100.0,
              height: 100.0,
              child: Image.network(
                url,
                fit: BoxFit.fitWidth, 
              ),
            ),
          ),
          onTap: () {
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => PhotoViewerWidget(url: url),
                fullscreenDialog: true,
              ),
            );
          },
        ),
      ),
    );
  }
}

class PhotoViewerWidget extends StatelessWidget {
  final String url;

  const PhotoViewerWidget({Key? key, required this.url}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Material(
      color: Colors.black87,
      child: InkWell(
        child: Hero(
          tag: url,
          child: Image.network(
            url,
            fit: BoxFit.fitWidth,
          ),
        ),
        onTap: () => Navigator.of(context).pop(),
      ),
    );
  }
}
burakcetn
  • 86
  • 5