How to prevent Hero animation from covering SliverAppBar?

Viewed 649

I have a Hero animation that should animate an image from a ListView to the background of a FlexibleSpaceBar inside of a SliverAppBar. This works all fine, except that the hero animation takes place on top of the SliverAppBar and when the animation is finished, the SliverAppBarsuddenly pops in on top of the background, which doesn't look so nice.

How can I either a) include the SliverAppBar in the hero transition so that it fades in on top of the image as the transition progresses or b) make the hero transition take place under the SliverAppBar without covering it?

Sample Code:

import 'dart:math';

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: '/',
      routes: {
        '/': (context) => HomePage(),
        '/details': (context) => DetailsPage(),
      },
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: PreferredSize(
        preferredSize: AppBar().preferredSize,
        child: Hero(
          tag: 'appbar',
          child: AppBar(
            title: Text('title'),
            actions: <Widget>[
              PopupMenuButton<String>(
                itemBuilder: (BuildContext context) {
                  return [
                    PopupMenuItem<String>(
                      child: Text('Action'),
                    )
                  ];
                },
              ),
            ],
          ),
        ),
      ),
      body: ListView.builder(
        padding: const EdgeInsets.fromLTRB(10, 10, 10, 0),
        itemCount: 10,
        itemBuilder: (context, index) => buildListItem(context, index),
      ),
    );
  }

  Widget buildListItem(BuildContext context, int index) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 10),
      child: InkWell(
        onTap: () async {
          await Navigator.of(context).pushNamed(
            '/details',
            arguments: index,
          );
        },
        child: Row(
          children: <Widget>[
            Hero(
              tag: index,
              child:
                  SizedBox(width: 80, height: 80, child: widgetForIndex(index)),
            ),
          ],
        ),
      ),
    );
  }
}

Widget widgetForIndex(int index) {
  return Container(color: Color.fromARGB(255, max(0, 255 - 20 * index), 0, 0));
}

class DetailsPage extends StatefulWidget {
  @override
  _DetailsPageState createState() => _DetailsPageState();
}

class _DetailsPageState extends State<DetailsPage> {
  @override
  Widget build(BuildContext context) {
    final int index = ModalRoute.of(context).settings.arguments;
    return Scaffold(
      body: CustomScrollView(
        slivers: [
          SliverAppBar(
            title: Text('Details'),
            expandedHeight: 300,
            flexibleSpace: FlexibleSpaceBar(
              background: Hero(
                tag: index,
                child: widgetForIndex(index),
              ),
            ),
          ),
          SliverToBoxAdapter(
            child: Column(
              children: [
                Container(
                  color: Colors.green,
                  width: 200,
                  height: 200,
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}
0 Answers
Related