Controllers in Flutter can be seen as a class that is responsible for change a certain value and notify listeners. From this perspective, they are very related to state management.
In the next update (4.2.0), I plan to add three controllers: InjectedAnimation
, InjectedScroll
, and InjectedTextEditing
to abstract the use of AnimationController
, ScrollController
, and TextEditingController
.
Let's start with the InjectedAnimation
:
With InjectedAnimation, you can set animation implicitly without any limitation or explicitly with practically no boilerplate.
Injecting animation:
First we need to inject the animation:
final animation = RM.injectAnimation(
//Required parameter
duration: const Duration(seconds: 2),
//Optional parameters
curve: Curves.linear,
repeats: 2,
shouldReverseRepeats: true,
endAnimationListener: (){
print('animation ends');
}
);
- You have to define the duration of the animation.
- The default curve is
Curves.linear
.
- If you want the animation to repeat a certain number of times, you define the “repeats” argument.
- If the animation is set to repeat, once the forward path is complete, it will go back to the beginning and start over.
- If
shouldReverseRepeats
is set to true, the animation will repeat the cycle from start to end and reverse from end to start and so on.
endAnimationListener
is used to performed side effects once the animation is finished.
Animation is auto disposed of once no longer used. So do not worry about disposing of it.
Implicit animation:
Let's reproduce the AnimatedContainer
example in official Flutter docs. (link here).
In Flutter AnimatedContainer
example, we see:
Center(
child: AnimatedContainer(
width: selected ? 200.0 : 100.0,
height: selected ? 100.0 : 200.0,
color: selected ? Colors.red : Colors.blue,
alignment: selected ? Alignment.center : AlignmentDirectional.topCenter,
duration: const Duration(seconds: 2),
curve: Curves.fastOutSlowIn,
child: const FlutterLogo(size: 75),
),
),
With states_rebuilder
animation, we simply use the Container
widget :
Center(
child: On.animation(
(animate) => Container(
// Animate is a callable class
width: animate.call(selected ? 200.0 : 100.0),
height: animate(selected ? 100.0 : 200.0, 'height'),
color: animate(selected ? Colors.red : Colors.blue),
alignment: animate(selected ? Alignment.center : AlignmentDirectional.topCenter),
child: const FlutterLogo(size: 75),
),
).listenTo(animation),
),
On.animation
or On.animation
is used to listen to the injected animation.
On.animation
exposes the animate
function.
- Using the exposed
animate
function, we set the animation start and end values.
- As the width and height are the same types (double), we need to add a name to distinguish them.
That's all, you are not limited to use a widget that starts with an Animated prefix to use implicit animation.
Explicit animation
In explicit animation, you have full control over how to parametrize your animation using tweens.
On.animation(
(animate) => Transform.rotate(
angle: animate.formTween(
(currentValue) => Tween(begin: 0, end: 2 * 3.14),
)!,
child: const FlutterLogo(size: 75),
),
).listenTo(animation),
- The
FlutterLogo
will rotate from 0 to 2 * 3.14 (one turn)
- The
fromTween
exposes the current value of the angle. It may be used to animate from the current value to the next value. (See the example below)
For rebuild performance use Child
, Child2
and Child3
widget.
Example of a Clock:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:states_rebuilder/states_rebuilder.dart';
final animation = RM.injectAnimation(
duration: const Duration(seconds: 1),
curve: Curves.easeInOut,
onInitialized: (animation) {
Timer.periodic(
Duration(seconds: 1),
(_) {
//rebuild the On.animation listeners, and recalculate the new implicit
//animation values
animation.refresh();
},
);
});
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Clock')),
body: MyStatefulWidget(),
),
);
}
}
class MyStatefulWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Container(
width: 200,
height: 200,
decoration: BoxDecoration(border: Border.all(width: 2.0)),
child: Align(
alignment: Alignment.topCenter,
child: Child3(
//Second rod
child1: Container(
width: 1,
height: 100,
color: Colors.red,
),
//minute rod
child2: Container(
width: 2,
height: 90,
color: Colors.black,
),
//hour rod
child3: Container(
width: 4,
height: 80,
color: Colors.black,
),
builder: (secondRod, minuteRod, hourRod) => Stack(
alignment: Alignment.bottomCenter,
children: [
On.animation(
(animate) => Transform.rotate(
angle: animate.formTween(
(currentValue) => Tween(
begin: currentValue ?? 0,
end: (currentValue ?? 0) + 2 * 3.14 / 60,
),
)!,
alignment: Alignment.bottomCenter,
child: secondRod,
),
).listenTo(animation),
On.animation(
(animate) => Transform.rotate(
angle: animate.formTween(
(currentValue) => Tween(
begin: currentValue ?? 0,
end: (currentValue ?? 0) + 2 * 3.14 / 60 / 60,
),
)!,
alignment: Alignment.bottomCenter,
child: minuteRod,
),
).listenTo(animation),
On.animation(
(animate) => Transform.rotate(
angle: animate.formTween(
(currentValue) => Tween(
begin: currentValue ?? 0,
end: (currentValue ?? 0) + 2 * 3.14 / 60 / 60 / 60,
),
)!,
alignment: Alignment.bottomCenter,
child: hourRod,
),
).listenTo(animation),
],
),
),
),
),
);
}
}
This is the output of this example:
Head to the wiki page to read more information about InjectedAnimation