DEV Community

Cover image for A month of Flutter: the real hero animation
Abraham Williams
Abraham Williams

Posted on • Originally published at bendyworks.com

A month of Flutter: the real hero animation

For the last post before the month's wrap up tomorrow, I wanted to do something more fun: use a hero animation between the home page list and the individual post page.

When I first implemented the Hero animation it never worked going back from a PostPage to the HomePage. The reason was that HomePage would get rerendered and that would generate new fake posts. So I moved the fake data generation up a level to MyApp and pass it into HomePage. This is more realistic as going to the HomePage shouldn't request the Posts every time.

HomePage( title: 'Birb', posts: _loadPosts(context), ) ~~~{% endraw %} The {% raw %}`PostPage`{% endraw %} implementation is a simple {% raw %}`StatelessWidget`{% endraw %} that takes {% raw %}`Post`{% endraw %} and renders a {% raw %}`PostItem`{% endraw %}. This will become more complex as things like comments and likes are implemented but works for now.{% raw %} ~~~dart class PostPage extends StatelessWidget { const PostPage({ Key key, @required this.post, }) : super(key: key); final Post post; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Post'), centerTitle: true, elevation: 0.0, ), body: SingleChildScrollView( child: Padding( padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 8.0), child: PostItem(post), ), ), ); } } ~~~{% endraw %} With {% raw %}`PostItem`{% endraw %} being used to render on the {% raw %}`HomePage`{% endraw %} and on the {% raw %}`PostPage`{% endraw %}, wrapping the {% raw %}`Image`{% endraw %} in a {% raw %}`Hero`{% endraw %} is handled in a single place. {% raw %}`tag`{% endraw %} is how {% raw %}`Hero`{% endraw %} knows what to transition between pages.{% raw %} ~~~dart Hero( tag: post.id, child: ClipRRect( child: Image.network(post.imageUrl), borderRadius: BorderRadius.circular(10.0), ), ) ~~~{% endraw %} The last piece is navigating from {% raw %}`PostList`{% endraw %} to {% raw %}`PostPage`{% endraw %} when a user taps on a {% raw %}`PostItem`{% endraw %}. I'll handle this with an [{% raw %}`InkWell` widget](https://docs.flutter.io/flutter/material/InkWell-class.html) so there is a nice [Material ripple](https://material.io/design/motion/understanding-motion.html#usage). ~~~dart InkWell( onTap: () => _navigateToPost(context, post), child: PostItem(post), ) ~~~ The navigation is more complex then [opening the registration page](https://bendyworks.com/blog/a-month-of-flutter-navigate-to-user-registration) for two reasons. [Named routes](https://flutter.io/docs/cookbook/navigation/named-routes) don't support parameters and I wanted a simple [transition](https://docs.flutter.io/flutter/widgets/PageRouteBuilder/buildTransitions.html) between the rest of the content on the page. ~~~dart void _navigateToPost(BuildContext context, Post post) { Navigator.of(context).push( PageRouteBuilder<PostPage>( pageBuilder: ( BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, ) { return PostPage(post: post); }, transitionsBuilder: ( BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child, ) { return FadeTransition( opacity: animation, child: child, ); }, ), ); } ~~~{% endraw %} Here I will [{% raw %}`push`{% endraw %}](https://docs.flutter.io/flutter/widgets/Navigator/push.html) a [{% raw %}`PageRouteBuilder`](https://docs.flutter.io/flutter/widgets/PageRouteBuilder-class.html) onto the navigation stack. `PageRouteBuilder` has two key builders in use here. `pageBuilder` builds the widget that should be rendered as the new page and `transitionBuilder` specifies how to transition between the old and new pages. Note that this [`FadeTransition`](https://docs.flutter.io/flutter/widgets/FadeTransition-class.html) is not related to implementing `Hero` earlier. The tests for {% raw %}`PostPage`{% endraw %} is simple and just checking that {% raw %}`PostItem`{% endraw %} is rendered. I did update the {% raw %}`PostItem`{% endraw %} test to expect that its {% raw %}`Hero`{% endraw %} widget had the correct {% raw %}`tag`{% endraw %} value.{% raw %} ~~~dart expect(tester.widget<Hero>(hero).tag, post.id); ~~~{% endraw %} {% raw %}`PostsList`{% endraw %} tests had to be wrapped in a {% raw %}`MaterialApp`{% endraw %} as [{% raw %}`InkWell`{% endraw %}](https://docs.flutter.io/flutter/material/InkWell-class.html) must have a Material widget ancestor. The navigation and animation from {% raw %}`PostsList`{% endraw %} to {% raw %}`PostPage`{% endraw %} is now doing more work so I replaced several [{% raw %}`pump`{% endraw %}](https://docs.flutter.io/flutter/flutter_test/WidgetTester/pump.html) pauses with [`pumpAndSettle`](https://docs.flutter.io/flutter/flutter_test/WidgetTester/pumpAndSettle.html). Here is the fancy {% raw %}`Hero`{% endraw %} animation: {% youtube 2u-_GOWhWNc %} ## Code changes {% github https://github.com/abraham/birb/issues/68 %} 
Enter fullscreen mode Exit fullscreen mode

Top comments (0)