Flutter loader: resize and rotate at the same time - flutter

I'm new to Flutter and would like to make a customized loader, but can't find a solution. When a user clicks on a button and some operation begins, I'd like to show a customized widget (basically a loader, notifying a user that operation has begun) that pops up to the center of the screen and grows in size (from 0x0 to 300x300), while it is rotating at the same time. When it reaches maximum size (300x300), I want it to shrink back to the size of 0x0 and hide/disappear, while rotating as well.
This animation should take 2 seconds. If after 2 seconds the operation is not completed, I'd like to start over with the animation.

You can create your own animations very easily. Using Animation and AnimationController you can do basically anything that you can think of.
Check out this video if you want to dig deeper into Animations with Flutter to make Complex UIs
To achieve what you are asking, you can build your Loading Indicator using a stateless widget.
Here is an interactive example
class MyLoadingIndicator extends StatefulWidget {
final Widget child;
const MyLoadingIndicator({
#required this.child,
Key key,
}) : super(key: key);
#override
_MyLoadingIndicatorState createState() => _MyLoadingIndicatorState();
}
class _MyLoadingIndicatorState extends State<MyLoadingIndicator> with TickerProviderStateMixin {
AnimationController rotateController;
Animation<double> rotateAnimation;
AnimationController scaleController;
Animation<double> scaleAnimation;
#override
void initState() {
super.initState();
rotateController = AnimationController(
duration: const Duration(seconds: 1),
reverseDuration: const Duration(seconds: 1),
vsync: this,
);
rotateAnimation = CurvedAnimation(parent: rotateController, curve: Curves.linear);
rotateController.repeat(
min: 0,
max: 1,
period: Duration(seconds: 2),
);
scaleController = AnimationController(
duration: const Duration(seconds: 1),
reverseDuration: const Duration(seconds: 1),
vsync: this,
);
scaleAnimation = CurvedAnimation(parent: scaleController, curve: Curves.linear);
scaleController.repeat(
min: 0,
max: 1,
period: Duration(seconds: 2),
reverse: true,
);
}
#override
void dispose() {
rotateController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return RotationTransition(
turns: rotateAnimation,
child: ScaleTransition(
scale: scaleAnimation,
child: Container(
height: 300,
width: 300,
color: Colors.red,
child: widget.child,
),
),
);
}
}

Related

Animation is Much Faster than the Duration Specified

I was making animation in flutter. When I run it on emulator, the duration of the animation is correct, but when I run it on my device the animation is about 5x faster.
I found this when I looked it up -
But still the duration of the animation for users will be different depending on their developer settings.
So how do I fix this duration issue.
I am using version 3.3.10 btw.
Just attaching my code :
import 'package:flutter/material.dart';
class BookmarkAnimation extends StatefulWidget {
const BookmarkAnimation({super.key});
#override
// ignore: library_private_types_in_public_api
_BookmarkAnimationState createState() => _BookmarkAnimationState();
}
class _BookmarkAnimationState extends State<BookmarkAnimation>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _animation;
bool _isBookmarked = false;
#override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 7500),
);
_animation = Tween(begin: 0.0, end: 1.0).animate(_animationController);
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
_isBookmarked = !_isBookmarked;
});
if (_animationController.isCompleted) {
_animationController.reverse();
} else {
_animationController.forward();
}
},
child: RotationTransition(
turns: _animation,
child: AnimatedCrossFade(
firstChild: const Icon(
Icons.bookmark_border,
),
secondChild: const Icon(
Icons.bookmark,
),
crossFadeState: _isBookmarked
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
duration: const Duration(milliseconds: 7500),
),
),
);
}
}
I found out it has to do with some settings in the developer options. But the issue is that animation for users will be different depending on these settings.
Actually, by default the animation speed is set to 1x in the Animation scale settings. If you want to make sure the animation speed is ok for all you may better to change your parameters. Or you may try this, find this file in the Android directory: AndroidManifest.xml and add this line:
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />

Postpone the start of the AnimationController animation in Flutter

I'm trying to delay the animation for 5 seconds from the beginning, here's my code snippet, but it doesn't work properly, as it would be better to delay for 5 seconds from the start of the startup screen. I tried to make a separate function that would be responsible for the delay but it also caused me to display errors on the emulator. I tried to use a ternary operator and delay it, but it also didn't work properly. I also want my animation to come in 7 seconds, I think it can also be done in a ternary operator. I don't yet know exactly how to stop the animation, so I used a long delay in the animation to make it invisible.
class BubbleBackground extends StatefulWidget {
final Widget child;
final double size;
const BubbleBackground({
Key key,
#required this.child,
#required this.size,
}) : super(key: key);
#override
State<BubbleBackground> createState() => _BubbleBackgroundState();
}
class _BubbleBackgroundState extends State<BubbleBackground>
with SingleTickerProviderStateMixin {
bool timer = false;
final longAnimationDuration = 100000;
final shortAnimationDuration = 700;
AnimationController _controller;
#override
void initState() {
setState(() {
// Future.delayed(Duration(seconds: 5), () {
// timer = true;
// });
timer = true;
});
_controller = AnimationController(
lowerBound: 0.9,
duration: Duration(
milliseconds: timer
? Future.delayed(
Duration(seconds: 5),
() {
shortAnimationDuration;
},
)
: longAnimationDuration,
),
vsync: this,
)..repeat(reverse: true);
super.initState();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (_, __) {
return Transform.scale(
scale: _controller.value,
child: Container(
width: widget.size,
height: widget.size,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
child: widget.child,
),
);
},
);
}
}
i figured out that I could just write initState normally, and I'll get desirable result for time delay
#override
void initState() {
super.initState();
_controller = AnimationController(
lowerBound: 0.9,
duration: Duration(milliseconds: shortAnimationDuration),
vsync: this,
);
Future.delayed(Duration(seconds: 5), () {
_controller.repeat(reverse: true);
});
}

Flutter staggered animation on same property

I want to animate the scale of a widget.
On tap down it should scale down and on tap up, scale up.
Problem is how can I use staggered animations on the same property?
There is an older question here on SO (Chaining seperate animations that work on the same properties) but it has no working answers.
Ive tried to adapt its solution but the problem is each animation listener is called even if the specified Interval has not been reached.
Ive added a condition which checks if _animationFuture is set but with this pattern you have to keep a value for any running animation you have in your widget which is a bit cumbersome.
Is there a "native" solution where you dont need a work around or are multiple animations on same value just not supported?
I do not want to reverse() the animation. Each tween must be able to have a custom curve, begin and end values.
Heres my widget:
class AnimatedIconButton extends StatefulWidget {
final Widget child;
final Function onPress;
AnimatedIconButton({#required this.child, this.onPress}): assert(child != null);
#override
_AnimatedIconButtonState createState() => _AnimatedIconButtonState();
}
class _AnimatedIconButtonState extends State<AnimatedIconButton> with SingleTickerProviderStateMixin {
AnimationController _animationController;
Animation<double> _animationIn;
Animation<double> _animationOut;
double _scale = 1;
TickerFuture _animationFuture;
Function get onPress => widget.onPress ?? () => null;
_tapDown(TapDownDetails details) {
print("_tapDown progress=${_animationController.value} scale=$_scale");
_animationFuture = _animationController.animateTo(0.5);
}
_tapUp(TapUpDetails details) {
assert(_animationFuture != null);
print("_tapUp progress=${_animationController.value} scale=$_scale");
_animationFuture.then((_) {
_animationFuture = null;
_animationController.animateTo(1);
});
}
#override
void initState() {
super.initState();
_animationController = AnimationController(value: 0, vsync: this, duration: const Duration(milliseconds: 1500), animationBehavior: AnimationBehavior.preserve);
_animationIn = Tween<double>(begin: 1.0, end: 0.9).animate(CurvedAnimation(
parent: _animationController,
curve: Interval(
0, 0.5, curve: Curves.easeOut,
),
))..addListener(() {
if (_animationFuture == null) return;
print("_animationIn.value ${_animationOut.value}");
_scale = _animationIn.value;
});
_animationOut = Tween<double>(begin: 0.9, end: 1).animate(CurvedAnimation(
parent: _animationController,
curve: Interval(
0.5, 1, curve: Curves.elasticOut,
),
))..addListener(() {
if (_animationFuture != null) return;
print("_animationOut.value ${_animationOut.value}");
_scale = _animationOut.value;
});
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTapUp: _tapUp,
onTapDown: _tapDown,
child: AnimatedBuilder(
animation: _animationController,
child: widget.child,
builder: (_, child) {
return Transform.scale(
scale: _scale,
transformHitTests: false,
child: child,
);
},
),
);
}
}
You can use a TweenSequence to stagger multiple animations behind each other. Borrowing the example from the documentation and changing it a bit, here's a copy-paste widget that animates a blue container from 100x100 to 200x200, pauses for a bit, and animates it back:
class _StaggeredState extends State<Staggered>
with SingleTickerProviderStateMixin {
/// Controller managing the animation
late final AnimationController controller;
/// The aninmation of our TweenSequence
late final Animation<double> animation;
/// An Animatable consisting of a series of tweens
final Animatable<double> tweenSequence = TweenSequence<double>(
<TweenSequenceItem<double>>[
// Animate from .5 to 1 in the first 40/80th of this animation
TweenSequenceItem<double>(
tween: Tween<double>(begin: 100, end: 200)
.chain(CurveTween(curve: Curves.ease)),
weight: 40.0,
),
// Maintain still at 1.0 for 20/80th
TweenSequenceItem<double>(
tween: ConstantTween<double>(200),
weight: 20.0,
),
// Animate back from 1 to 0.5 for the last 40/80th
TweenSequenceItem<double>(
tween: Tween<double>(begin: 200, end: 100)
.chain(CurveTween(curve: Curves.ease)),
weight: 40.0,
),
],
);
#override
void initState() {
super.initState();
controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 1),
);
// Animate our TweenSequence using our controller
animation = tweenSequence.animate(controller);
// Add a listener that calls [setState] so our widget rebuilds
// when the animation value changes.
controller.addListener(() => setState(() {}));
// Set the controller to repeat indefinitely
controller.repeat();
}
#override
void dispose() {
super.dispose();
controller.dispose();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Container(
width: animation.value,
height: animation.value,
color: Colors.blue,
),
),
),
);
}
}

Flutter, the second animation controller is not animating, why?

I have two animation controllers using TickerProviderStateMixin the first animation is working smoothly while the second does not animate when I trigger it's forward method : Here are their declarations :
class HomeAnimator extends StatefulWidget {
#override
_HomeAnimatorState createState() => _HomeAnimatorState();
}
class _HomeAnimatorState extends State<HomeAnimator>
with TickerProviderStateMixin {
AnimationController _controller;
AnimationController _signupctrl;
#override
void initState() {
super.initState();
_controller =
AnimationController(duration: Duration(milliseconds: 900), vsync: this);
_signupctrl =
AnimationController(duration: Duration(milliseconds: 900), vsync: this);
// _controller.forward();
}
#override
void dispose() {
super.dispose();
_controller.dispose();
_signupctrl.dispose();
}
#override
Widget build(BuildContext context) {
return Dahome(controllers: [_controller,_signupctrl]);
}
}
I have two files for the different set of animations :
-- Mahome_EnterAnimation: when clicked on sign-in button
here's a relevant piece :
class EntAnime {
EntAnime(this.controller)
: opanime = Tween<double>(begin: 1, end: 0).animate(CurvedAnimation(
parent: controller,
curve: Interval(0, 0.5, curve: Curves.fastOutSlowIn),
)),
r_opanime = Tween<double>(begin: 0, end: 1).animate(CurvedAnimation(
parent: controller,
curve: Interval(0, 0.5, curve: Curves.fastOutSlowIn),
)),
hfact = Tween<double>(begin: 0, end: 1).animate(CurvedAnimation(
parent: controller,
curve: Interval(0, 0.5, curve: Curves.fastOutSlowIn),
)),
-- Mahome_SignupAnime: for when I press sign-up button
class SignupAnime {
SignupAnime(this.controller):
qopanime = Tween<double>(begin: 0, end: 1).animate(CurvedAnimation(
parent: controller,
curve: Interval(0.0, 0.5, curve: Curves.fastOutSlowIn),
)),
qhfact = Tween<double>(begin: 0, end: 1).animate(CurvedAnimation(
parent: controller,
curve: Interval(0.5, 0.9, curve: Curves.fastOutSlowIn),
));
final AnimationController controller;
final Animation<double> qopanime;
final Animation<double> qhfact;
}
This is how I declared it :
class Dahome extends StatefulWidget {
Dahome({Key key, this.title, #required List<AnimationController> controllers})
: daanime = EntAnime(controllers[0]),
signanime = SignupAnime(controllers[1]),
super(key: key);
final EntAnime daanime;
final SignupAnime signanime;
final String title;
Dastate createState() => Dastate(daanime, signanime);
// #override
}
class Dastate extends State<Dahome> {
Dastate(this.maanime, this.loganime);
final EntAnime maanime;
final SignupAnime loganime;
...
I called the forward methods in two different buttons:
The first one here and the second the same way:
RaisedButton(
onPressed: () => {
maanime.controller.forward()
},
Silly me, the problem was the animatedbuilder widget I had to change the animation parameter with the second controller

Rotating container indefinitely

I would like to rotate an Image indefinitely.
This container is one of the widget within the stack and would like this to be rotating continuously non stop.
final AnimationController animation = AnimationController(
duration: const Duration(milliseconds: 1800),
vsync: const NonStopVSync(),
)..repeat();
final Tween tween = Tween(begin: 0.0, end: math.pi);
var square = Container(
width: 100,
height: 100,
transform: Matrix4.identity(),
color: Colors.amber,
);
...
class Foo extends State<Bar> {
...
animation.addListener((){
square.transform = Matrix4.rotationZ(tween.evaluate(animation));
});
Widget build(BuildContext context) {
return Stack(
children: [
...
Center(
child: square
)
]
)
}
}
and I get this error: 'transform' can't be used as a setter because it's final. (assignment_to_final at [digital_clock] lib/digital_clock.dart:139)
How would I do what I'm trying to do?
Try something like this:
class InfiniteAnimation extends StatefulWidget {
final Widget child;
final int durationInSeconds;
InfiniteAnimation({#required this.child, this.durationInSeconds = 2,});
#override
_InfiniteAnimationState createState() => _InfiniteAnimationState();
}
class _InfiniteAnimationState extends State<InfiniteAnimation>
with SingleTickerProviderStateMixin {
AnimationController animationController;
Animation<double> animation;
​
#override
void initState() {
super.initState();
animationController = AnimationController(
vsync: this,
duration: Duration(seconds: widget.durationInSeconds),
);
animation = Tween<double>(
begin: 0,
end: 12.5664, // 2Radians (360 degrees)
).animate(animationController);
​
animationController.forward();
​
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
animationController.repeat();
}
});
}
​
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: animationController,
builder: (context, child) => Transform.rotate(
angle: animation.value,
child: widget.child,
),
);
}
#override
void dispose() {
animationController?.dispose();
super.dispose();
}
}
You basically need to create a StatefulWidget that mixes in (with keyword) the SingleTickerProviderStateMixin, provide an AnimationController, start the animation, then when the animation completes, repeat.
The AnimationBuilder is a better way of telling the widget to update on every frame without having to listen to the animationController and call setState explicitly.
You can use it like this:
InfiniteAnimation(
durationInSeconds: 2, // this is the default value
child: Icon(
Icons.expand_more,
size: 50.0,
color: Colors.white,
),
)