Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Animate ListView item to full screen in Flutter

I would like to have my list items perform this animation (mp4) when tapped. I tried using AnimatedCrossFade but it requires its two children to be at the same level, e.g. the detail view cross-fades with the ListView not the tapped item. In fact it seems a Hero animation is the only one that can animate across widgets.

I'm having trouble using Hero. Should it wrap the list item? Does it matter if the Widget subtree is significantly different in the Hero source/destination? Also, can Hero animations be used with LocalHistoryRoutes or staggered animations?

Edit

It's now looking like what I need to do is use an Overlay, the hard part there is that I need to add the selected item to the overlay at the same position on screen where it was tapped, then the animation part would be easy. Possibly of use here is a target/follower pattern e.g. CompositedTransformTarget

like image 208
Jacob Phillips Avatar asked May 21 '18 21:05

Jacob Phillips


2 Answers

Someone wrote an amazing dart-package for just this purpose: https://pub.dev/packages/morpheus#-readme-tab-

All you then need to do is use the MorpheusPageRoute and the package handles the rest.

...
Navigator.push(
  context,
  MorpheusPageRoute(
    builder: (context) => MyWidget(title: title),
  ),
);
...
like image 134
Lucas Aschenbach Avatar answered Nov 13 '22 12:11

Lucas Aschenbach


You can just use Hero widget to make that kind of animation. Here's my example:

enter image description here

and the source code:

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new FirstPage(title: 'Color Palette'),
    );
  }
}

class FirstPage extends StatefulWidget {
  FirstPage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _FirstPageState createState() => new _FirstPageState();
}

class _FirstPageState extends State<FirstPage> {
  final palette = [
    {'#E53935': 0xFFE53935},
    {'#D81B60': 0xFFD81B60},
    {'#8E24AA': 0xFF8E24AA},
    {'#5E35B1': 0xFF5E35B1},
    {'#3949AB': 0xFF3949AB},
    {'#1E88E5': 0xFF1E88E5},
    {'#039BE5': 0xFF039BE5},
    {'#00ACC1': 0xFF00ACC1},
    {'#00897B': 0xFF00897B},
    {'#43A047': 0xFF43A047},
    {'#7CB342': 0xFF7CB342},
    {'#C0CA33': 0xFFC0CA33},
  ];

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Container(
        child: new ListView.builder(
            itemCount: palette.length,
            itemBuilder: (context, index) => new Hero(
                  tag: palette[index].keys.first,
                  child: new GestureDetector(
                    onTap: () {
                      Navigator
                          .of(context)
                          .push(new ColorPageRoute(palette[index]));
                    },
                    child: new Container(
                      height: 64.0,
                      width: double.infinity,
                      color: new Color(palette[index].values.first),
                      child: new Center(
                        child: new Hero(
                          tag: 'text-${palette[index].keys.first}',
                          child: new Text(
                            palette[index].keys.first,
                            style: Theme.of(context).textTheme.title.copyWith(
                                  color: Colors.white,
                                ),
                          ),
                        ),
                      ),
                    ),
                  ),
                )),
      ),
    );
  }
}

class SecondPage extends StatelessWidget {
  final Map<String, int> color;

  SecondPage({this.color});

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Color'),
      ),
      body: new Hero(
        tag: color.keys.first,
        child: new Container(
          color: new Color(color.values.first),
          child: new Center(
            child: new Hero(
              tag: 'text-${color.keys.first}',
              child: new Text(
                color.keys.first,
                style:
                    Theme.of(context).textTheme.title.copyWith(color: Colors.white),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class ColorPageRoute extends MaterialPageRoute {
  ColorPageRoute(Map<String, int> color)
      : super(
            builder: (context) => new SecondPage(
                  color: color,
                ));

  @override
  Widget buildTransitions(BuildContext context, Animation<double> animation,
      Animation<double> secondaryAnimation, Widget child) {
    return FadeTransition(opacity: animation, child: child);
  }
}
like image 35
hunghd Avatar answered Nov 13 '22 12:11

hunghd