How to create a hero style animation on the same screen route with Flutter? How to create a hero style animation on the same screen route with Flutter? dart dart

How to create a hero style animation on the same screen route with Flutter?


You will need a Sliver to animate your layout based on the scroll offset. More specifically, SliverPersistentHeader in your situation.

CustomMultiChildLayout is not necessary though, you can achieve the same result using tweens and align/padding/stuff. But you can give it a go if your layout starts to become too complex.

The trick is to use the scroll offset given by SliverPersistentHeader to compute the current progression. Then use that progression to position element between their original and final position.

Here's a raw example:

enter image description here

class TransitionAppBar extends StatelessWidget {  final Widget avatar;  final Widget title;  const TransitionAppBar({this.avatar, this.title, Key key}) : super(key: key);  @override  Widget build(BuildContext context) {    return SliverPersistentHeader(      pinned: true,      delegate: _TransitionAppBarDelegate(        avatar: avatar,        title: title,      ),    );  }}class _TransitionAppBarDelegate extends SliverPersistentHeaderDelegate {  final _avatarTween =      SizeTween(begin: Size(150.0, 150.0), end: Size(50.0, 50.0));  final _avatarMarginTween =      EdgeInsetsTween(begin: EdgeInsets.zero, end: EdgeInsets.only(left: 10.0));  final _avatarAlignTween =      AlignmentTween(begin: Alignment.topCenter, end: Alignment.centerLeft);  final _titleMarginTween = EdgeInsetsTween(      begin: EdgeInsets.only(top: 150.0 + 5.0),      end: EdgeInsets.only(left: 10.0 + 50.0 + 5.0));  final _titleAlignTween =      AlignmentTween(begin: Alignment.center, end: Alignment.centerLeft);  final Widget avatar;  final Widget title;  _TransitionAppBarDelegate({this.avatar, this.title})      : assert(avatar != null),        assert(title != null);  @override  Widget build(      BuildContext context, double shrinkOffset, bool overlapsContent) {    final progress = shrinkOffset / 200.0;    final avatarSize = _avatarTween.lerp(progress);    final avatarMargin = _avatarMarginTween.lerp(progress);    final avatarAlign = _avatarAlignTween.lerp(progress);    final titleMargin = _titleMarginTween.lerp(progress);    final titleAlign = _titleAlignTween.lerp(progress);    return Stack(      fit: StackFit.expand,      children: <Widget>[        Padding(          padding: avatarMargin,          child: Align(            alignment: avatarAlign,            child: SizedBox.fromSize(size: avatarSize, child: avatar),          ),        ),        Padding(          padding: titleMargin,          child: Align(            alignment: titleAlign,            child: DefaultTextStyle(                style: Theme.of(context).textTheme.title, child: title),          ),        )      ],    );  }  @override  double get maxExtent => 200.0;  @override  double get minExtent => 100.0;  @override  bool shouldRebuild(_TransitionAppBarDelegate oldDelegate) {    return avatar != oldDelegate.avatar || title != oldDelegate.title;  }}

which you can use with a CustomScrollView:

Scaffold(  body: CustomScrollView(    slivers: <Widget>[      TransitionAppBar(        avatar: Material(          color: Colors.blue,          elevation: 3.0,        ),        title: Text("Hello World"),      ),      SliverList(        delegate: SliverChildBuilderDelegate((context, index) {          return ListTile(            title: Text('$index'),          );        }),      )    ],  ),);