How to run an animation in loop - flutter

I'm shaking a widget using the below code, but the effect is once,
how do I keep it running in a loop at timed intervals. I believe it can be done by changing the key but it's a final and can't be changed.
import 'package:flutter/material.dart';
#immutable
class ShakeWidget extends StatelessWidget {
final Duration duration;
final double deltaX;
final Widget child;
final Curve curve;
const ShakeWidget({
Key key,
this.duration = const Duration(milliseconds: 500),
this.deltaX = 20,
this.curve = Curves.bounceOut,
this.child,
}) : super(key: key);
/// convert 0-1 to 0-1-0
double shake(double animation) =>
2 * (0.5 - (0.5 - curve.transform(animation)).abs());
#override
Widget build(BuildContext context) {
return TweenAnimationBuilder<double>(
key: key,
tween: Tween(begin: 0.0, end: 1.0),
duration: duration,
builder: (context, animation, child) => Transform.translate(
offset: Offset(deltaX * shake(animation), 0),
child: child,
),
child: child,
);
}
}

You need to use AnimationController
And call repeat when the controller is completed
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
body: SafeArea(
child: ShakeWidget(
child: Text('Hello world'),
),
),
),
);
}
}
class ShakeWidget extends StatefulWidget {
const ShakeWidget({
Key key,
this.duration = const Duration(milliseconds: 500),
this.deltaX = 20,
this.curve = Curves.bounceOut,
#required this.child,
}) : super(key: key);
final Duration duration;
final double deltaX;
final Widget child;
final Curve curve;
#override
_ShakeWidgetState createState() => _ShakeWidgetState();
}
class _ShakeWidgetState extends State<ShakeWidget>
with SingleTickerProviderStateMixin {
AnimationController controller;
#override
void initState() {
super.initState();
controller = AnimationController(
duration: widget.duration,
vsync: this,
)
..forward()
..addListener(() {
if (controller.isCompleted) {
controller.repeat();
}
});
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
/// convert 0-1 to 0-1-0
double shake(double value) =>
2 * (0.5 - (0.5 - widget.curve.transform(value)).abs());
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: controller,
builder: (context, child) => Transform.translate(
offset: Offset(widget.deltaX * shake(controller.value), 0),
child: child,
),
child: widget.child,
);
}
}

Use an AnimationController, like so:
AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
)..repeat();

The simple_animations package comes with the LoopAnimation widget which is meant for exactly this. This also means you don't have to create an AnimationController so the code's a lot cleaner.
LoopAnimation(
builder: (context, child, double value) {
return Transform.rotate(
angle: pi * value,
child: const Icon(Icons.notifications),
);
},
duration: const Duration(seconds: 1),
tween: Tween<double>(begin: 0, end: 2),
);
If you want to run an animation forward and backward continuously to get a yo-yo effect, you can use the MirrorAnimation widget instead.

Here is the repeating animation sample, from start → end then from end ← start
#override
void initState() {
super.initState();
_animationController =
AnimationController(vsync: this, duration: widget.duration);
_animation = widget.tween.animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeIn));
_animationController.forward();
// The looping is done via listener.
_animationController.addListener(() {
if (_animationController.isCompleted) {
_animationController.reverse();
}
if(_animationController.isDismissed){
_animationController.forward();
}
});
}
Don't forget to call _animationController.dispose() in the state's dispose() method.

Related

change icon color when sliding animation to top in flutter

I need to change the color of the icon while sliding to the top. Using two colors, one at the beginning and the second at the end.
I tried this code below, but the color remains the same.
class Loading extends StatefulWidget {
const Loading({Key? key}) : super(key: key);
static String tag = '/Loading';
#override
State<Loading> createState() => _LoadingState();
}
class _LoadingState extends State<Loading> with TickerProviderStateMixin {
late AnimationController controller, colorController;
late Animation<Offset> offset;
late Animation animation;
#override
void initState() {
super.initState();
controller =
AnimationController(duration: Duration(seconds: 1), vsync: this);
Future.delayed(Duration(milliseconds: 100))
.then((_) => controller.forward());
offset = Tween<Offset>(begin: Offset(0.0, 10.0), end: Offset.zero)
.animate(controller);
colorController = AnimationController(
duration:
const Duration(milliseconds: 5000), //controll animation duration
vsync: this,
);
animation = ColorTween(
begin: Colors.grey,
end: Colors.red,
).animate(colorController);
}
// #override
// void dispose() {
// controller2.dispose();
// super.dispose();
// }
#override
Widget build(BuildContext context) {
return Scaffold(
body: Align(
alignment: Alignment.center,
child: SlideTransition(
position: offset,
child: Icon(
Icons.favorite,
color: animation.value,
),
),
));
}
}
You are missing to call forward on colorController and to change the UI use addListener to call setState.
colorController = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
)
..addListener(() {
setState(() {});
})
..forward();

Rotation Transition to rotate anticlockwise flutter

I have this code that makes the icon rotate clockwise. I want to make it rotate anticlockwise. how can I do that?
animationController =
AnimationController(vsync: this, duration: Duration(seconds: 3))
..repeat(reverse: true);
RotationTransition(
turns: animationController,
child: Icon(Icons.settings_sharp),
alignment: Alignment.center,
)
Concerning RotationTransition widget rotation direction is manipulated by turns property.
When widget rotates clockwise controllers value goes from 0.0 to 1.0.
To rotate in opposite direction you need value to go from 1.0. to 0.0.
To achieve that, you can set up Tween:
final Tween<double> turnsTween = Tween<double>(
begin: 1,
end: 0,
);
And animate this tween in turns property with any AnimationController you need:
child: RotationTransition(
turns: turnsTween.animate(_controller),
...
Sample result and code to reproduce:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
/// This is the main application widget.
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
/// This is the stateful widget that the main application instantiates.
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
/// This is the private State class that goes with MyStatefulWidget.
/// AnimationControllers can be created with `vsync: this` because of TickerProviderStateMixin.
class _MyStatefulWidgetState extends State<MyStatefulWidget>
with TickerProviderStateMixin {
late final AnimationController _controller = AnimationController(
duration: const Duration(seconds: 4),
vsync: this,
)..repeat();
final Tween<double> turnsTween = Tween<double>(
begin: 1,
end: 0,
);
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RotationTransition(
turns: turnsTween.animate(_controller),
child: const Padding(
padding: EdgeInsets.all(8.0),
child: FlutterLogo(size: 150.0),
),
),
),
);
}
}
You can add in an AnimationBuilder and Animation object and set the end Tween property to negative.
Also, It looked like you were attempting to have the widget reverse rotation upon completion in a cycle? If so, I added in the code for that too if it helps :)
For example:
#override
void initState() {
_controller =
AnimationController(vsync: this, duration: Duration(seconds: 3))
//To have the widget change rotation direction upon completion
..addStatusListener((status) {
if(status == AnimationStatus.completed)
_controller.reverse();
if(status == AnimationStatus.dismissed){
_controller.forward();
}
});
_animTurn = Tween<double>(begin: 0.0, end: -1.0).animate(_controller);
_controller.forward();
super.initState();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return
RotationTransition(
turns: _animTurn,
child: Icon(Icons.settings_sharp,size: 100,),
alignment: Alignment.center,
);
});
}
}

AnimatedBuilder with two different types of animations not working

I'm trying to animate the color and the value of a CircularProgressIndicator I want it to go from 0-1 and from pink to orange in 15 seconds. Here's everything I tried:
TweenAnimationBuilder<double>(
tween: Tween<double>(begin: 0, end: 1),
curve: Curves.easeOut,
duration: Duration(seconds: 15),
builder: (context, value, _) {
Color strokeColor = Colors.pink;
if (value > 0.5) strokeColor = Colors.orange;
return CircularProgressIndicator(
strokeWidth: 8, value: value, valueColor: AlwaysStoppedAnimation<Color>(strokeColor));
}),
That animated the value, but not the valueColor/stroke color. I have to somehow pass ColorTween(begin: Colors.pink, end: Colors.orange) to the valueColor, but I'm not sure how... I replacing the TweenAnimationBuilder with a AnimatedBuilder, but I ran into issues since the animation can only use 1 type of value, i.e. either a double or a color, but can't do both...
It's possible to use a AnimatedBuilder to pass a ColorTween and a Tween to control the color and the value of the CircularProgressIndicator, respectively.
In order to do so:
We need to use the SingleTickerProviderStateMixin on our parent StatefulWidget:
class _MyProgressIndicator extends State<MyProgressIndicator>
with SingleTickerProviderStateMixin {
// ...
}
Initialize the AnimationController, ColorTween, Tween<double>, and starting the AnimationController by overriding the initState() method:
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 15),
vsync: this,
);
_animationColor = ColorTween(
begin: Colors.pink,
end: Colors.orange,
).animate(_controller);
_animationValue = Tween<double>(begin: 0, end: 1).animate(_controller);
_controller.forward();
}
Don't forget the AnimationController by overriding the dispose() method!
#override
void dispose() {
_controller.dispose();
super.dispose();
}
A full example is displayed below:
import 'package:flutter/material.dart';
class MyProgressIndicator extends StatefulWidget {
MyProgressIndicator({Key key}) : super(key: key);
#override
_MyProgressIndicator createState() => _MyProgressIndicator();
}
class _MyProgressIndicator extends State<MyProgressIndicator>
with SingleTickerProviderStateMixin {
Animation<Color> _animationColor;
Animation<double> _animationValue;
AnimationController _controller;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 15),
vsync: this,
);
_animationColor = ColorTween(
begin: Colors.pink,
end: Colors.orange,
).animate(_controller);
_animationValue = Tween<double>(begin: 0, end: 1).animate(_controller);
_controller.forward();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget child) {
return CircularProgressIndicator(
strokeWidth: 8,
value: _animationValue.value,
valueColor: _animationColor,
);
},
),
),
);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
}

RotationY Matrix4 not rotating fully

I have a text widget I want to rotate for 360 degrees only on Y axis.
Here is the code I currently have, it only rotates up to a certain point.
import 'package:flutter/material.dart';
class Loader extends StatefulWidget {
#override
_LoaderState createState() => _LoaderState();
}
class _LoaderState extends State<Loader> with SingleTickerProviderStateMixin {
Animation _rotate;
AnimationController _controller;
#override
void initState() {
super.initState();
_controller = new AnimationController(
vsync: this,
duration: new Duration(seconds: 1),
);
_rotate = Tween(begin: 0.0, end: 3.14 * 2).animate(_controller);
_controller.forward();
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
#override
Widget build(BuildContext context) {
return Container(
child: AnimatedBuilder(
animation: _controller,
child: Text("Flutter"),
builder: (BuildContext context, Widget _widget) {
return Transform(
transform: Matrix4.rotationY(_controller.value),
child: _widget,
);
},
),
);
}
}
Do you guys have any idea how to fix this?
Thanks!
add this
_rotate = Tween(begin: 0.0, end: 3.14 * 2).animate(_controller)
..addListener(() {
setState(() {});
});

How can I make a "show up" text animation in Flutter?

I want to achieve the kind of behavior shown in the material.io page:
The text does a nice transition when is being shown for the first time.
How can I do that in Flutter?
You can compose widgets like this:
import 'dart:async';
import 'package:flutter/material.dart';
class ShowUp extends StatefulWidget {
final Widget child;
final int delay;
ShowUp({#required this.child, this.delay});
#override
_ShowUpState createState() => _ShowUpState();
}
class _ShowUpState extends State<ShowUp> with TickerProviderStateMixin {
AnimationController _animController;
Animation<Offset> _animOffset;
#override
void initState() {
super.initState();
_animController =
AnimationController(vsync: this, duration: Duration(milliseconds: 500));
final curve =
CurvedAnimation(curve: Curves.decelerate, parent: _animController);
_animOffset =
Tween<Offset>(begin: const Offset(0.0, 0.35), end: Offset.zero)
.animate(curve);
if (widget.delay == null) {
_animController.forward();
} else {
Timer(Duration(milliseconds: widget.delay), () {
_animController.forward();
});
}
}
#override
void dispose() {
super.dispose();
_animController.dispose();
}
#override
Widget build(BuildContext context) {
return FadeTransition(
child: SlideTransition(
position: _animOffset,
child: widget.child,
),
opacity: _animController,
);
}
}
Then you can use it like this:
int delayAmount = 500;
...........
...........
...........
Column(
children: <Widget>[
ShowUp(
child: Text("The first texto to be shown"),
delay: delayAmount,
),
ShowUp(
child: Text("The text below the first"),
delay: delayAmount + 200,
),
ShowUp(
child: Column(
children: <Widget>[
Text("Texts together 1"),
Text("Texts together 2"),
Text("Texts together 3"),
],
),
delay: delayAmount + 400,
),
],
),
Note that this "ShowUp" widgets can animate anything, not just texts.
There's an existing package called show_up_animation for it which is based on the implementation below.
This is a generalized widget to provide this animation. All you have to do is to wrap your widget(yes, any widget) inside SlideFadeTransition widget and voila!
It gives a lot of control. For example, you can control the amount, speed, direction, delay and even the curve of the animation.
///Wrapper class to implement slide and fade animations at the same time to
///a given element. Wrap the widget that you wish to appear with slide-fade
///transition in this class.
import 'dart:async';
import 'package:flutter/material.dart';
enum Direction { vertical, horizontal }
class SlideFadeTransition extends StatefulWidget {
///The child on which to apply the given [SlideFadeTransition]
final Widget child;
///The offset by which to slide and [child] into view from [Direction].
///Defaults to 0.2
final double offset;
///The curve used to animate the [child] into view.
///Defaults to [Curves.easeIn]
final Curve curve;
///The direction from which to animate the [child] into view. [Direction.horizontal]
///will make the child slide on x-axis by [offset] and [Direction.vertical] on y-axis.
///Defaults to [Direction.vertical]
final Direction direction;
///The delay with which to animate the [child]. Takes in a [Duration] and
/// defaults to 0.0 seconds
final Duration delayStart;
///The total duration in which the animation completes. Defaults to 800 milliseconds
final Duration animationDuration;
SlideFadeTransition({
#required this.child,
this.offset = 0.2,
this.curve = Curves.easeIn,
this.direction = Direction.vertical,
this.delayStart = const Duration(seconds: 0),
this.animationDuration = const Duration(milliseconds: 800),
});
#override
_SlideFadeTransitionState createState() => _SlideFadeTransitionState();
}
class _SlideFadeTransitionState extends State<SlideFadeTransition>
with SingleTickerProviderStateMixin {
Animation<Offset> _animationSlide;
AnimationController _animationController;
Animation<double> _animationFade;
#override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: widget.animationDuration,
);
//configure the animation controller as per the direction
if (widget.direction == Direction.vertical) {
_animationSlide =
Tween<Offset>(begin: Offset(0, widget.offset), end: Offset(0, 0))
.animate(CurvedAnimation(
curve: widget.curve,
parent: _animationController,
));
} else {
_animationSlide =
Tween<Offset>(begin: Offset(widget.offset, 0), end: Offset(0, 0))
.animate(CurvedAnimation(
curve: widget.curve,
parent: _animationController,
));
}
_animationFade =
Tween<double>(begin: -1.0, end: 1.0).animate(CurvedAnimation(
curve: widget.curve,
parent: _animationController,
));
Timer(widget.delayStart, () {
_animationController.forward();
});
}
#override
Widget build(BuildContext context) {
return FadeTransition(
opacity: _animationFade,
child: SlideTransition(
position: _animationSlide,
child: widget.child,
),
);
}
}
To use it, you just have to wrap your text widget or any widget for that matter with SlideFadeTransition widget. Below is a complete working example.
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Show up Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Show up Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SlideFadeTransition(
child: Text(
'You have pushed the button this many times:',
),
),
SlideFadeTransition(
delayStart: Duration(milliseconds: 800),
child: Text(
'0',
style: Theme.of(context).textTheme.display1,
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {});
},
tooltip: 'Animate',
child: Icon(Icons.add),
),
);
}
}
///Wrapper class to implement slide and fade animations at the same time to
///a given element. Wrap the widget that you wish to appear with slide-fade
///transition in this class.
enum Direction { vertical, horizontal }
class SlideFadeTransition extends StatefulWidget {
///The child on which to apply the given [SlideFadeTransition]
final Widget child;
///The offset by which to slide and [child] into view from [Direction].
///Defaults to 1.0
final double offset;
///The curve used to animate the [child] into view.
///Defaults to [Curves.easeIn]
final Curve curve;
///The direction from which to animate the [child] into view. [Direction.horizontal]
///will make the child slide on x-axis by [offset] and [Direction.vertical] on y-axis.
///Defaults to [Direction.vertical]
final Direction direction;
///The delay with which to animate the [child]. Takes in a [Duration] and
/// defaults to 0.0 seconds
final Duration delayStart;
///The total duration in which the animation completes. Defaults to 800 milliseconds
final Duration animationDuration;
SlideFadeTransition({
#required this.child,
this.offset = 1.0,
this.curve = Curves.easeIn,
this.direction = Direction.vertical,
this.delayStart = const Duration(seconds: 0),
this.animationDuration = const Duration(milliseconds: 800),
});
#override
_SlideFadeTransitionState createState() => _SlideFadeTransitionState();
}
class _SlideFadeTransitionState extends State<SlideFadeTransition>
with SingleTickerProviderStateMixin {
Animation<Offset> _animationSlide;
AnimationController _animationController;
Animation<double> _animationFade;
#override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: widget.animationDuration,
);
//configure the animation controller as per the direction
if (widget.direction == Direction.vertical) {
_animationSlide =
Tween<Offset>(begin: Offset(0, widget.offset), end: Offset(0, 0))
.animate(CurvedAnimation(
curve: widget.curve,
parent: _animationController,
));
} else {
_animationSlide =
Tween<Offset>(begin: Offset(widget.offset, 0), end: Offset(0, 0))
.animate(CurvedAnimation(
curve: widget.curve,
parent: _animationController,
));
}
_animationFade =
Tween<double>(begin: -1.0, end: 1.0).animate(CurvedAnimation(
curve: widget.curve,
parent: _animationController,
));
Timer(widget.delayStart, () {
_animationController.forward();
});
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return FadeTransition(
opacity: _animationFade,
child: SlideTransition(
position: _animationSlide,
child: widget.child,
),
);
}
}
I built a package to do this simply.
delayed_widget
Example:
DelayedWidget(
delayDuration: Duration(milliseconds: 200),// Not required
animationDuration: Duration(seconds: 1),// Not required
animation: DelayedAnimations.SLIDE_FROM_BOTTOM,// Not required
child: Container(
width: 200,
height: 200,
color: Colors.red,
))