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.
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).
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 build
to 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 Animation
to 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.
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.
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.
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.
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.
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.
To avoid this, we must use the property flipped
for the curve we want to use.
switchInCurve: Curves.easeInBack,
switchOutCurve: Curves.easeInBack.flipped,
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 …
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”