ref –
- http://chineseruleof8.com/code/index.php/2021/05/06/15084/
- http://chineseruleof8.com/code/index.php/2021/05/02/fade-and-slide-transition-in-flutter/
- Download Source
- result
We start off at main.dart. Its just a standard Scaffold that Navigates a SecondPage (stateful widget) class onto the screen.
When SecondPage appears, we need to keep track of state because we need an AnimationController called _transitionUpAndFadeInCtrller to do some animation. We use a Timer to wait 100 milliseconds before we execute our animationController. Hence, this means that the animation is executed when the screen appears.
SecondPage.dart
1 2 3 4 5 6 7 8 9 10 11 |
@override void initState() { _transitionUpAndFadeInCtrller = AnimationController( vsync: this, duration: Duration(milliseconds: 1000)); // wait 500 milli before we execute our animation print("Execute animation!!!"); Timer(Duration(milliseconds: 100), () => _transitionUpAndFadeInCtrller.forward()); super.initState(); } |
We then pass the _transitionUpAndFadeInCtrller into another child class Bottom that we want to animate.
SecondPage.dart
1 |
Bottom(introAnimateCtrller: this._transitionUpAndFadeInCtrller), |
In that child class we’re going to animate a Slide Transition. We specify how it will animate by saying it will begin at 20% from the bottom (0, 0.2). In this case, positive number at Y moves it down. Negative number moves it up.
example:
1 Y means we start from below the x-axis.
so 0.5 means we push 50% of the height down. Halfway of the box is on x-axis.
and then we end up with full height of 280 after animation.
-1 Y means we start from TOP of the x-axis.
so -0.5 means we start from above the x-axis.
We animate the container up to the original position at (0,0).
Bottom.dart
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class _BottomState extends State<Bottom> with SingleTickerProviderStateMixin { final AnimationController introAnimateCtrller; ... ... textAndButtonsContainer() { return Container(...); } @override Widget build(BuildContext context) { ... SlideTransition( position: Tween<Offset>( begin: Offset(0, 0.2), // ANIMATE DOWN 20% end: Offset(0, 0), // end at full height of 280 ).animate(this.introAnimateCtrller), child: textAndButtonsContainer(), ) .. } |
So the animation controller specified that the animation duration will be 1 second. The animation itself is a SlideTransition that tweens from (0, 0.2) to (0,0) on a container with text and buttons.
Let’s also add a fade transition to it. We put the fade transition as a child of the slide transition. We give the animation controller as opacity because it would animate opacity 0 to 1 in the duration specified by the animation controller.
Bottom.dart
1 2 3 4 5 6 7 8 9 10 |
SlideTransition( position: Tween<Offset>( begin: Offset(0, 0.2), // ANIMATE DOWN 20% end: Offset(0, 0), // end at full height of 280 ).animate(animationController), child: FadeTransition( opacity: this.introAnimateCtrller, child: textAndButtonsContainer(), ) ); |
So when the screen appears, we do the slide Transition along with the fade transition for a container that has text and buttons.
Adding in an Animation for future usage
So we have a nice animation going when the screen appears. Now we want to add animation so that in the future, we can animate the container of text and buttons off the screen like this:
The way to do this is to first create a separate animation controller. We then initialize it to AnimationController with vsync referencing this, and a duration in milliseconds.
Bottom.dart
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 |
class _BottomState extends State<Bottom> with SingleTickerProviderStateMixin { bool bPushed = false; AnimationController animateEnterPwdController; _BottomState({@required this.introAnimateCtrller}); void initState() { animateEnterPwdController = AnimationController( vsync: this, duration: Duration(milliseconds: NEXT_SCREEN_DURATION)); animateEnterPwdController.addStatusListener(animationStatusListener); super.initState(); } void animationStatusListener(AnimationStatus status) { if (status == AnimationStatus.completed) { print('animation complete!'); setState(() { bPushed = true; }); this.introAnimateCtrller.reverse(); } else if (status == AnimationStatus.dismissed) { print('animation dismissed!'); } } ... ... } |
bPushed and animationStatusListener
a) the boolean bPushed
b) and animateEnterPwdController.addStatusListener(animationStatusListener).
The status listeners tells us when the slide away animation has completed. It will work together with bPushed. So what ends up happening is that when the button in our container is pushed, we will animate the container with the text and button OFF SCREEN using animateEnterPwdController. When the animation is complete, our bPushed will be set to true. Once it’s true, we create the stagger animation list.
Bottom.dart
1 |
bPushed ? staggerAnimationList() : Container(), |
Remember that when state is changed, the whole instance is refreshed. Thus, the build function will run, and we get to re-draw this part of the DOM tree with the stagger animation list.
The slide away animation using animateEnterPwdController
We wrap animation AROUND the UI container.
Bottom.dart
1 2 3 4 5 6 7 8 9 10 |
SlideTransition( position: Tween<Offset>( begin: Offset(0.0, 0), // ANIMATE LEFT 100% end: Offset(-1, 0), ).animate(this.animateEnterPwdController), child: FadeTransition( opacity: this.introAnimateCtrller, child: textAndButtonsContainer(), ), ), |
Notice that there is two animations wrapped around textAndButtonsContainer:
1) introAnimateCtrller
2) animateEnterPwdController
Understand that the contents are visible because introAnimateCtrller animated them onto the screen. It has executed already and is finished. We then use animateEnterPwdController to animate this container OFF the screen. This is the simple logic to it. The animation itself is very straightforward as it uses a Tween on a SlideTransition to move the container off screen to the left.
Once the container animates off screen, our listener will trigger:
1 2 3 4 5 6 7 |
if (status == AnimationStatus.completed) { print('animation complete!'); setState(() { bPushed = true; }); this.introAnimateCtrller.reverse(); } |
We simply do two things. Set bPushed to true so that state has changed. This means our build function will be called again with the bPushed of true, which then draws the staggered animation:
1 2 3 4 5 6 |
@override Widget build(BuildContext context) { return Stack(alignment: Alignment.bottomCenter, children: <Widget>[ bPushed ? staggerAnimationList() : Container(), ... ... |
Notice
1 |
this.introAnimateCtrller.reverse(); |
We’re basically reversing the intro animation, which will then move toward and fade away from opacity 1 to 0.