In this flutter tutorial, we learn to use animations in flutter. In Flutter Animation tutorial, we will learn to create a custom widget that can be used anywhere in flutter to animate any widget.
In our previous tutorials, we already learned how to make the Splash Screen with animations and also how to create a Welcome Screen. So, I will explain the animations based on the previous two tutorials so if you are new you can watch last both here…
USEFUL LINKS
- Install Android Studio
- Install Flutter
- Create a new flutter app
- Create Folder Structure
- Setup Theme in Flutter for Light & Dark Mode
This is the tutorial of our Flutter Login App Series.
Design Custom Widget
First of all, we will learn to design Custom Widget, which will contain all the animated widgets.
If you are using GetX state management then wrap it with Obx().
If you are using Stateful Widget then no need to wrap it in any widget.
You can customize this widget with any format, but we will use AnimatedPositioned and AnimatedOpacity Widgets, as we already learned them in detail in our Splash Screen Animation tutorial.
class TFadeInAnimation extends StatelessWidget {
TFadeInAnimation({
Key? key,
required this.durationInMs,
required this.child,
this.animate,
}) : super(key: key);
final controller = Get.put(FadeInAnimationController());
final int durationInMs;
final TAnimatePosition? animate;
final Widget child;
@override
Widget build(BuildContext context) {
return Obx(
() => AnimatedPositioned(
duration: Duration(milliseconds: durationInMs),
top: controller.animate.value ? animate!.topAfter : animate!.topBefore,
left:
controller.animate.value ? animate!.leftAfter : animate!.leftBefore,
bottom: controller.animate.value
? animate!.bottomAfter
: animate!.bottomBefore,
right: controller.animate.value
? animate!.rightAfter
: animate!.rightBefore,
child: AnimatedOpacity(
duration: Duration(milliseconds: durationInMs),
opacity: controller.animate.value ? 1 : 0,
child: child,
),
),
);
}
}
Animation Widget Model
Models are the best way to organize our code. Models are used in programming to map data either from database or from any other source.
In our case we will get the flutter Animation properties in model.
class TAnimatePosition{
final double? topBefore, bottomBefore, leftBefore, rightBefore;
final double? topAfter, bottomAfter, leftAfter, rightAfter;
TAnimatePosition ({
this.topBefore,
this.bottomBefore,
this.leftBefore,
this.rightBefore,
this.topAfter,
this.bottomAfter,
this.leftAfter,
this.rightAfter,
});
}
Animation Controller
Like other classes, we will also create a separate controller for our animation. The basic reason is to separate the Logic from the Design and increase the readability.
If you are using GetX then you should use Controller otherwise you have to add your logic in your animation_design.dart file.
In controller we can create 3 methods, animateSplashScreen(), animateIn() & amimateOut(). Animate In & Out will be used for all the screens other then splash.
class FadeInAnimationController extends GetxController {
static FadeInAnimationController get find => Get.find();
RxBool animate = false.obs;
//To be used in Splash Screen due to auto calling of next activity.
Future startSplashAnimation() async {
await Future.delayed(const Duration(milliseconds: 500));
animate.value = true;
await Future.delayed(const Duration(milliseconds: 3000));
animate.value = false;
await Future.delayed(const Duration(milliseconds: 2000));
Get.off(
// Get.off Instead of Get.offAll()
() => const WelcomeScreen(),
duration: const Duration(milliseconds: 1000), //Transition Time
transition: Transition.fadeIn, //Screen Switch Transition
);
}
//Call where you need to animate In any widget.
Future animationIn() async {
await Future.delayed(const Duration(milliseconds: 500));
animate.value = true;
}
//Call where you need to animate Out any widget.
Future animationOut() async {
animate.value = false;
await Future.delayed(const Duration(milliseconds: 100));
}
}
Tip: While navigation, if screen is blinking or doing irregular behavior, replace Get.offAll() to Get.off() as Get.off() only replace the previous screen.
Use Custom Animation Widget
Once the custom animation widget is created. Now, we can easily use it to animate any widget in flutter.
Just wrap the widget with TFadeInAnimation() and pass the duration, animate, and child.
Also don’t forget to call the start animation at the top of stateless Class.
final controller = Get.put(FadeInAnimationController());
controller.startSplashAnimation();
TFadeInAnimation(
durationInMs: 1600,
animate: TAnimatePosition(topAfter: 0, topBefore: -30, leftBefore: -30, leftAfter: 0),
child: const Image(image: AssetImage(tSplashTopIcon)),
),
- animation_design.dart
- fade_in_animation_controller.dart
- fade_in_animation_model.dart
- splash_screen.dart
import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:get/get_core/src/get_main.dart'; import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart'; import '../../constants/image_strings.dart'; import 'fade_in_animation_controller.dart'; import 'fade_in_animation_model.dart'; class TFadeInAnimation extends StatelessWidget { TFadeInAnimation({ Key? key, required this.durationInMs, required this.child, this.animate, }) : super(key: key); final controller = Get.put(FadeInAnimationController()); final int durationInMs; final TAnimatePosition? animate; final Widget child; @override Widget build(BuildContext context) { return Obx( () => AnimatedPositioned( duration: Duration(milliseconds: durationInMs), top: controller.animate.value ? animate!.topAfter : animate!.topBefore, left: controller.animate.value ? animate!.leftAfter : animate!.leftBefore, bottom: controller.animate.value ? animate!.bottomAfter : animate!.bottomBefore, right: controller.animate.value ? animate!.rightAfter : animate!.rightBefore, child: AnimatedOpacity( duration: Duration(milliseconds: durationInMs), opacity: controller.animate.value ? 1 : 0, child: child, ), ), ); } }
class FadeInAnimationController extends GetxController {
static FadeInAnimationController get find => Get.find();
RxBool animate = false.obs;
Future startSplashAnimation() async {
await Future.delayed(const Duration(milliseconds: 500));
animate.value = true;
await Future.delayed(const Duration(milliseconds: 3000));
animate.value = false;
await Future.delayed(const Duration(milliseconds: 2000));
Get.off( // Get.off Instead of Get.offAll()
() => const WelcomeScreen(),
duration: const Duration(milliseconds: 1000), //Transition Time
transition: Transition.fadeIn, //Screen Switch Transition
);
}
//Can be used to animate In after calling the next screen.
Future animationIn() async {
await Future.delayed(const Duration(milliseconds: 500));
animate.value = true;
}
//Can be used to animate Out before calling the next screen.
Future animationOut() async {
animate.value = false;
await Future.delayed(const Duration(milliseconds: 100));
}
}
class TAnimatePosition{
final double? topBefore, bottomBefore, leftBefore, rightBefore;
final double? topAfter, bottomAfter, leftAfter, rightAfter;
TAnimatePosition ({
this.topBefore,
this.bottomBefore,
this.leftBefore,
this.rightBefore,
this.topAfter,
this.bottomAfter,
this.leftAfter,
this.rightAfter,
});
}
class SplashScreen extends StatelessWidget {
const SplashScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final controller = Get.put(FadeInAnimationController());
controller.startSplashAnimation();
return Scaffold(
body: Stack(
children: [
TFadeInAnimation(
durationInMs: 1600,
animate: TAnimatePosition(
topAfter: 0, topBefore: –30, leftBefore: –30, leftAfter: 0,
),
child: const Image(image: AssetImage(tSplashTopIcon)),
),
TFadeInAnimation(
durationInMs: 2000,
animate: TAnimatePosition(topBefore: 80, topAfter: 80, leftAfter: tDefaultSize, leftBefore: –80),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(tAppName,
style: Theme.of(context).textTheme.headline3),
Text(tAppTagLine,
style: Theme.of(context).textTheme.headline2)
],
),
),
TFadeInAnimation(
durationInMs: 2400,
animate: TAnimatePosition(bottomBefore: 0, bottomAfter: 100),
child: const Image(image: AssetImage(tSplashImage)),
),
TFadeInAnimation(
durationInMs: 2400,
animate: TAnimatePosition(bottomBefore: 0, bottomAfter: 60, rightBefore: tDefaultSize, rightAfter: tDefaultSize),
child: Container(
width: tSplashContainerSize,
height: tSplashContainerSize,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100),
color: tPrimaryColor
),
),
),
],
),
);
}
}