Flutter: flip animation

Translation prepared as part of the course Flutter Mobile Developer

We invite everyone to the second day of the two-day online intensive “We create a Flutter application for Web, iOS and Android”… We continue to write the application (this will be a network application: loading a list of entities + filtering them + making a settings block in the application, where you can change the application theme (colors)). Join us!


When I first saw the widget AnimationSwitcher, then I thought that I could turn it over, revealing its reverse side.

I was wrong: AnimationSwitcher allows … to switch between different widgets with the animation you specify (the default animation is a fading transition). This component is too versatile for this purpose.

I had to read carefully ...
I had to read carefully …

Its use is very general, so I will show you how you can do this kind of animation.

I discovered a flutter package that can do this kind of flip animation, called animated_card_switcherbut it doesn’t seem to be properly supported and the code is too complex.

Here are the development steps:

  • Create front and back widgets

  • Use the AnimationSwitcher widget for animation

  • Create your own transition maker to rotate your card.

  • Add curves

Create front and back widgets

For this example, I will use simplified versions of the front and rear widgets, because it is not that essential.

The only thing to keep in mind is that you must set a key for the top-level widgets so that AnimationSwitcher found that the widget had changed (and therefore did the animation).

Like a piece of cake!
Like a piece of cake!

Here’s an example of the widget layout I’ll be using:

Widget __buildLayout({Key key, String faceName, Color backgroundColor}) {
  return Container(
    key: key,
    decoration: BoxDecoration(
      shape: BoxShape.rectangle,
      borderRadius: BorderRadius.circular(20.0),
      color: backgroundColor,
    ),
    child: Center(
      child: Text(faceName.substring(0, 1), style: TextStyle(fontSize: 80.0)),
    ),
  );

So my widgets will have front and back views:

Widget _buildFront() {
  return __buildLayout(
    key: ValueKey(true),
    backgroundColor: Colors.blue,
    faceName: "F",
  );
}

Widget _buildRear() {
  return __buildLayout(
    key: ValueKey(false),
    backgroundColor: Colors.blue.shade700,
    faceName: "R",
  );
}

Using the AnimationSwitcher Widget to Animate

Now we can use the widget AnimationSwitcher to animate the transition between the front and back sides.

IN StatefulWidget i am overriding the method buildto create a page that displays animation in its center.

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

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool _displayFront;
  bool _flipXAxis;

  @override
  void initState() {
    super.initState();
    _displayFront = true;
    _flipXAxis = true;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(this.widget.title),
        centerTitle: true,
      ),
      body: Center(
        child: Container(
          constraints: BoxConstraints.tight(Size.square(200.0)),
          child: _buildFlipAnimation(),
      ),
    );
  }
}

I have separated the animation from the page in the method _build Flip Animationto make the code clearer.

Here’s the first version of this method:

Widget _buildFlipAnimation() {
  return GestureDetector(
    onTap: () => setState(() =>_showFrontSide = !_showFrontSide),
    child: AnimatedSwitcher(
      duration: Duration(milliseconds: 600),
      child: _showFrontSide ? _buildFront() : _buildRear(),
    ),
  );
}

When we click on the widget, we see that the front of the widget disappears, revealing the back. When you click again, the back view disappears, revealing the front side.

At least something is happening.
At least something is happening.

We want to rotate the widgets along the Y-axis. Hopefully AnimationSwitcher will allow us to override the transition, thanks to the input transitionBuilder

Code your custom transition constructor to rotate your card

So here’s the blueprint: we want to get a 180 ° rotation (pi). We will wrap our widgets in AnimatedBuidler and use the widget Transform to apply rotation.

Widget __transitionBuilder(Widget widget, Animation<double> animation) {
  final rotateAnim = Tween(begin: pi, end: 0.0).animate(animation);
  return AnimatedBuilder(
    animation: rotateAnim,
    child: widget,
    builder: (context, widget) {
      return Transform(
        transform: Matrix4.rotationY(value),
        child: widget,
        alignment: Alignment.center,
      );
    },
  );
}

This is a good start, but not quite the right thing to do. We can see that when animating the transition, the back widget is on top from start to finish.

The final part appears too quickly.
The final part appears too quickly.

We want the front widget to gradually replace the back one.

Therefore, two things need to be changed:

  • The display order must be reversed: the replaced widget must be at the top of the stack.

  • In the middle of the animation, the replaceable widget should disappear.

To do this, we will change the input layoutBuilder our instance AnimationSwitcher

layoutBuilder: (widget, list) => Stack(children: [widget, ...list]),

Then, in the middle of the animation, as a result of the pi / 2 rotation, the widget’s width will be 0.0. Therefore, we will block this rotation (only) to animate the previous widget.

Widget __transitionBuilder(Widget widget, Animation<double> animation) {
  final rotateAnim = Tween(begin: pi, end: 0.0).animate(animation);
  return AnimatedBuilder(
    animation: rotateAnim,
    child: widget,
    builder: (context, widget) {
      final isUnder = (ValueKey(_showFrontSide) != widget.key);
      final value = isUnder ? min(rotateAnim.value, pi / 2) : rotateAnim.value;
      return Transform(
        transform: Matrix4.rotationY(value),
        child: widget,
        alignment: Alignment.center,
      );
    },
  );
}

Better, but we’re not done yet! To enhance the feeling of the widget being rotated, we will add a slight “tilt” to the widgets.

We need depth ... Sweet little fluffy depth.
We need depth … Sweet little fluffy depth.

This “skew” value should be 0.0 at the start and end of the animation. In addition, since we will be animating each side of our widget, their tilt should be the opposite. For example, if the tilt of the front widget is 0.2, then the tilt of the back widget should be -0.2.

To apply the tilt to the widget, we manually assign one specific value to the Matrix4 object that defines the rotation.

Widget __transitionBuilder(Widget widget, Animation<double> animation) {
  final rotateAnim = Tween(begin: pi, end: 0.0).animate(animation);
  return AnimatedBuilder(
    animation: rotateAnim,
    child: widget,
    builder: (context, widget) {
      final isUnder = (ValueKey(_showFrontSide) != widget.key);
      var tilt = ((animation.value - 0.5).abs() - 0.5) * 0.003;
      tilt *= isUnder ? -1.0 : 1.0;
      final value = isUnder ? min(rotateAnim.value, pi / 2) : rotateAnim.value;
      return Transform(
        transform: Matrix4.rotationY(value)..setEntry(3, 0, tilt),
        child: widget,
        alignment: Alignment.center,
      );
    },
  );
}

More information on Matrix4 can be found here: https://medium.com/flutter-community/advanced-flutter-matrix4-and-perspective-transformations-a79404a0d828

Add curves

Finally, to add some energy and dynamics to your animation, you can change the parameters of the AnimationSwitcher’s input curves.

Curves are always better!
Curves are always better!

Here’s my first try:

Widget _buildFlipAnimation() {
  return GestureDetector(
    onTap: _switchCard,
    child: AnimatedSwitcher(
      duration: Duration(milliseconds: 4600),
      transitionBuilder: __transitionBuilder,
      layoutBuilder: (widget, list) => Stack(children: [widget, ...list]),
      child: _showFrontSide ? _buildFront() : _buildRear(),
      switchInCurve: Curves.easeInBack,
      switchOutCurve: Curves.easeOutBack,
    ),
  );
}

I have to show you in slow motion what the problem is.

Oh Nooooo….
Oh Nooooo….

Since the curves are not completely identical, and the previous widget animation plays backwards, you get an artifact where the two widgets do not overlap properly. There will be a slight shift between them.

Animation in multi-frame mode ...
Animation in multi-frame mode …

To avoid this, we must use the property flipped for the curve we want to use.

switchInCurve: Curves.easeInBack,
switchOutCurve: Curves.easeInBack.flipped,
Sloooow Mooooo (now perfect)
Sloooow Mooooo (now perfect)

Conclusion

As you can see, nothing special: I made this animation from about 30 lines of code (animation only) using only one attribute (the displayed side of the widget).

I don’t think it makes sense to create a package for this. Adding a dependency to your code means that if it doesn’t work when a version is updated (for example), your project will stall for a certain amount of time. Even worse, if the dependency is no longer supported, you cannot guarantee that your project will compile correctly in the next 6 months, 1 or 2 years …

Copy and paste this example wisely :)
Copy and paste this example wisely 🙂

So feel free to use my code. I am not a fan of copy-paste, so to put it more accurately (feel free to use the code) “corresponding to my code”, that is, copy-paste it, understand and change according to your needs!

The demo used in this article is you will find here

Follow the Flutter community at Twitter


Learn more about the course Flutter Mobile Developer

Participate in a two-day online intensive “We create a Flutter application for Web, iOS and Android”

Similar Posts

Leave a Reply