ref – https://www.youtube.com/watch?v=xIX9Bo_yjvo
https://medium.com/codechai/flutter-animation-basics-explained-with-stacked-cards-9d34108403b8
First we have to refactor our class into a StatefulWidget.
1) extends StatefulWidget
2) @override createState()
3) extends State
4) with SingleTickerProviderStateMixin
When you add your SingleTickerProviderStateMixin, it tells Flutter that there is some animation in this widget and this widget needs to notified about the animation frames of flutter.
1 2 3 4 5 6 7 8 9 |
class SecondPage extends StatefulWidget { @override _SecondPageState createState() => _SecondPageState(); } class _SecondPageState extends State<SecondPage> with SingleTickerProviderStateMixin { ... } |
Animation controller is one of the basic things necessary for creating magic in flutter. You can imagine this as the dictator of the animation on screen. It contains the duration of the animation and so it will split out values between 0 to 1 based on the duration and the ticker value.
Controller value is 0 -> Start of your animation
Controller value is 1 -> End of your animation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class _SecondPageState extends State<SecondPage> with SingleTickerProviderStateMixin { AnimationController _animationController; @override void initState() { _animationController = AnimationController( vsync: this, duration: Duration(milliseconds: 1500)); _animationController.forward(); // get it running! super.initState(); } ... ... } |
vsync property is related to the Ticker and if the stateful widget has the Mixin, you can pass this to this property.
Animation with just the controller is possible when your animated value is also from just 0 to 1. You can pass the controller value to the widget and start the animation by calling controller.forward()
Tween
Short for in-betweening, the process of generating intermediate frames between two images to give the appearance that the first image evolves smoothly into the second image.
1 2 3 4 |
Tween<Offset>( begin: Offset(-1, 0), // -1 left side (x, y) START end: Offset.zero, ).animate(animationController) |
Creating the in-between values is the responsibility of Tween. Tween takes a begin ,end, properties and to create a Animation
When you call animation.forward() it will produce a smooth animation from the begin Offset value to end Offset value.
Fading in a Text
1 2 3 4 5 |
Text('An interesting title', style: TextStyle( fontSize: 24, ), ), |
Wrap some padding around it using a Container
1 2 3 4 5 |
Container( padding: EdgeInsets.all(16.0), alignment: Alignment.centerLeft, child: // Our Text Widget here ); |
Now let’s start a fade transition. We pass in our animation Controller for the opacity. Our animation controller has an animation time of 1500 milliseconds. Thus, it will animate the opacity from -1 to 0 in 1500 milliseconds.
1 2 3 4 |
FadeTransition( opacity: animationController, child: // Our Container Text here ); |
Run the project and you’ll the text have this fade effect.
Transition the Text
Let’s remove the fade transition from our Text Container. Our Text container should simply be:
1 2 3 4 5 6 |
Container( padding: EdgeInsets.all(16.0), alignment: Alignment.centerLeft, child: Text('An interesting title', style: TextStyle(fontSize: 24)), ), |
We wrap a SlideTransition like so:
1 2 3 4 5 6 7 |
SlideTransition( position: Tween<Offset>( begin: Offset(-1, 0), // -1 left side (x, y) START end: Offset.zero, ).animate(animationController), child: // Our Text Container here ); |
child class
So in order to make this work, we have a child class and that houses Text and an Image.
1 2 3 4 5 6 7 |
class TextAndImage extends StatelessWidget { final AnimationController animationController; TextAndImage({@required this.animationController}); ... ... } |
As you can see, it references an injected animation controller. The animation controller in the parent class holds state:
SlideTransition – for position property we assign it a Tween. The tween takes the animationController in order to animate.
1 2 3 4 |
Tween<Offset>( begin: Offset(-1, 0), // -1 left side (x, y) START end: Offset.zero, ).animate(animationController) |
FadeTransition – for opacity, we stick in the animation controller, which says duration should be 1.5 seconds.
However, we decide to forgo this feature and just use transition.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
class TextAndImage extends StatelessWidget { final AnimationController animationController; TextAndImage({@required this.animationController}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(top: 50.0), child: SafeArea( child: Column( children: <Widget>[ SlideTransition( position: Tween<Offset>( begin: Offset(-1, 0), // -1 left side (x, y) START end: Offset.zero, ).animate(animationController), child: Container( padding: EdgeInsets.all(16.0), alignment: Alignment.centerLeft, child: Text( 'An interesting title', style: TextStyle( fontSize: 24, ), ), ), ), Padding( padding: const EdgeInsets.all(8.0), child: ClipRRect( borderRadius: BorderRadius.all(Radius.circular(15)), child: Align( heightFactor: 0.826, alignment: Alignment.topLeft, child: Image.network( 'https://images.pexels.com/photos/320014/pexels-photo-320014.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=400', fit: BoxFit.fitWidth, ), ), ), ), ], ), ), ); } } |
In the parent class, we use it like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Second Page'), centerTitle: true, ), body: Stack( children: <Widget>[ TextAndImage(animationController: this._animationController), Bottom(animationController: this._animationController), ], ), ); } |
Now when the animation starts, the text will transition from the left and come in towards the right.
Moving the button up
So now using the same tactic, we create another class called Bottom, that will reference the parent class animationController. It will have some static UI widgets which we will animate.
1 2 3 4 5 6 7 8 9 |
class Bottom extends StatelessWidget { final AnimationController animationController; Bottom({ @required this.animationController, }); ... ... } |
For its build function, we return a slide Transition.
1 2 3 4 5 6 7 8 |
@override Widget build(BuildContext context) { return SlideTransition( position: ... ... ) } |
The position needs a Tween. We want it to come from bottom off screen
1 2 3 4 |
Tween<Offset>( begin: Offset(0, 1), // x, y end: Offset.zero, ).animate(animationController) |
So we know that it will slide something from offscreen (x is 0, y is 1) up to original position.
That something is a container that holds text:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
Container( padding: EdgeInsets.all(10), height: 180, color: Theme.of(context).primaryColor, child: Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text('Nice Kitty!', style: TextStyle( fontSize: 22, color: Theme.of(context).primaryTextTheme.headline5.color, shadows: [ Shadow( color: Colors.black, offset: Offset(1, 1), blurRadius: 2) ], ), ), ], ), ), |