Skip to content

Hero animation jumps when transitioning between Images with different fit #20510

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Jordan-Nelson opened this issue Aug 13, 2018 · 9 comments
Closed
Assignees
Labels
a: animation Animation APIs a: quality A truly polished experience f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels.

Comments

@Jordan-Nelson
Copy link

Jordan-Nelson commented Aug 13, 2018

Hero animations have a sudden jump when animating an image with two different fit values. It is very possible that Hero animations are not intended to work in this scenario. If that is the case, the documentation should clarify this. The example in the documentation says the following about fit.

Setting the Image’s fit property to BoxFit.contain, ensures that the image is as large as possible during the transition without changing its aspect ratio

This implies that the BoxFit does not have to be the same on each image.

Steps to Reproduce

Below is an application that demonstrates the issue. This application is a modified version on the application from the docs. In this application, the animation from screen 1 to screen 2 has a noticeable jump as the fit changes. However, the animation from screen 2 back to screen 1 animates perfectly.

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

void main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(primaryColor: Colors.white),
      home: new HeroAnimation(),
    );
  }
}

class HeroAnimation extends StatelessWidget {
  Widget build(BuildContext context) {
    timeDilation = 4.0;
    return Scaffold(
      appBar: AppBar(
        title: const Text('Screen 1'),
      ),
      body: Container(
        child: PhotoHero(
          photo:
              'https://www.emanprague.com/en/wp-content/uploads/2018/05/flutter_eman_blog.png',
          width: 400.0,
          height: 500.0,
          fit: BoxFit.cover,
          onTap: () {
            Navigator.of(context).push(MaterialPageRoute<Null>(
                builder: (BuildContext context) {
                  return Scaffold(
                      appBar: AppBar(
                        title: const Text('Screen 2'),
                      ),
                      body: Center(
                        child: PhotoHero(
                          photo:
                              'https://www.emanprague.com/en/wp-content/uploads/2018/05/flutter_eman_blog.png',
                          fit: BoxFit.contain,
                          onTap: () {
                            Navigator.of(context).pop();
                          },
                        ),
                      ));
                },
                fullscreenDialog: true));
          },
        ),
      ),
    );
  }
}

class PhotoHero extends StatelessWidget {
  const PhotoHero(
      {Key key, this.photo, this.onTap, this.width, this.height, this.fit})
      : super(key: key);

  final String photo;
  final VoidCallback onTap;
  final double width;
  final double height;
  final BoxFit fit;

  Widget build(BuildContext context) {
    return SizedBox(
      width: width,
      height: height,
      child: Hero(
        tag: photo,
        child: Material(
          color: Colors.transparent,
          child: InkWell(
            onTap: onTap,
            child: Image.network(
              photo,
              fit: fit,
            ),
          ),
        ),
      ),
    );
  }
}

GIF:

Url to GIF: https://user-images.githubusercontent.com/20613561/44007746-de559400-9e68-11e8-83ad-88f0fcd72bc3.gif

@zoechi zoechi added framework flutter/packages/flutter repository. See also f: labels. a: animation Animation APIs f: material design flutter/packages/flutter/material repository. a: quality A truly polished experience labels Aug 23, 2018
@zoechi zoechi added this to the Goals milestone Aug 23, 2018
@erf
Copy link

erf commented Feb 15, 2019

I'm also experience this. Would love to see this fixed.

@AlexV525
Copy link
Member

When hero pop from BoxFit.contain to BoxFit.cover in PageView, it will scale to absolutly 100% width and 100% height. See #31543

@AlexV525
Copy link
Member

@LongCatIsLooong Any progress about this issue?

@chunhtai
Copy link
Contributor

This is by design. according to document https://api.flutter.dev/flutter/widgets/Hero-class.html

To make the animations look good, it's critical that the widget tree for the hero in both locations be essentially identical. The widget of the target is, by default, used to do the transition: when going from route A to route B, route B's hero's widget is placed over route A's hero's widget. If a flightShuttleBuilder is supplied, its output widget is shown during the flight transition instead.

The identical also include the attribute which the box fit. I can update the document if it is confusing.

The hero animation only change the parent box size and offset, it has no idea about how children render inside the box, and that is why we encourage to keep the child the same.

If this is not possible, you should build your own flightShuttleBuilder. For your example

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

void main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(primaryColor: Colors.white),
      home: new HeroAnimation(),
    );
  }
}

class HeroAnimation extends StatelessWidget {
  Widget build(BuildContext context) {
    timeDilation = 4.0;
    return Scaffold(
      appBar: AppBar(
        title: const Text('Screen 1'),
      ),
      body: Container(
        child: PhotoHero(
          photo:
          'https://www.emanprague.com/en/wp-content/uploads/2018/05/flutter_eman_blog.png',
          width: 400.0,
          height: 500.0,
          fit: BoxFit.cover,
          onTap: () {
            Navigator.of(context).push(MaterialPageRoute<Null>(
              builder: (BuildContext context) {
                return Scaffold(
                  appBar: AppBar(
                    title: const Text('Screen 2'),
                  ),
                  body: Center(
                    child: PhotoHero(
                      photo:
                      'https://www.emanprague.com/en/wp-content/uploads/2018/05/flutter_eman_blog.png',
                      fit: BoxFit.contain,
                      onTap: () {
                        Navigator.of(context).pop();
                      },
                    ),
                  ));
              },
              fullscreenDialog: true));
          },
        ),
      ),
    );
  }
}

class PhotoHero extends StatelessWidget {
  const PhotoHero(
    {Key key, this.photo, this.onTap, this.width, this.height, this.fit})
    : super(key: key);

  final String photo;
  final VoidCallback onTap;
  final double width;
  final double height;
  final BoxFit fit;

  Widget build(BuildContext context) {
    return SizedBox(
      width: width,
      height: height,
      child: Hero(
        flightShuttleBuilder: (
            BuildContext flightContext,
            Animation<double> animation,
            HeroFlightDirection flightDirection,
            BuildContext fromHeroContext,
            BuildContext toHeroContext,
          ) {
          final Hero hero = flightDirection == HeroFlightDirection.push ? fromHeroContext.widget : toHeroContext.widget;
          return hero.child;
        },
        tag: photo,
        child: Material(
          color: Colors.transparent,
          child: InkWell(
            onTap: onTap,
            child: Image.network(
              photo,
              fit: fit,
            ),
          ),
        ),
      ),
    );
  }
}

There is no plan to change the current behavior. Let me know if there is other use case that cannot be achieve by using flightShuttleBuilder.

@chunhtai chunhtai added the waiting for customer response The Flutter team cannot make further progress on this issue until the original reporter responds label Aug 9, 2019
@chunhtai
Copy link
Contributor

I will close this issue for now, feel free to reopen if this is still an issue

@Jordan-Nelson
Copy link
Author

Thanks @chunhtai! I did not know about flightShuttleBuilder. This looks like a good solution.

@no-response no-response bot removed the waiting for customer response The Flutter team cannot make further progress on this issue until the original reporter responds label Oct 5, 2019
@no-response no-response bot reopened this Oct 5, 2019
@cherrybiu
Copy link

The flightShuttleBuilder will raise another problem. It will change its shape at the last moment which looks abrupt and strange.

@chaojian
Copy link

The flightShuttleBuilder will raise another problem. It will change its shape at the last moment which looks abrupt and strange.

can you fix this problem?

@github-actions
Copy link

This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 10, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
a: animation Animation APIs a: quality A truly polished experience f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels.
Projects
None yet
Development

No branches or pull requests

7 participants