Flutter How can I make this animation "smoother" - flutter

How can I make this animation "smoother" (currently is restarts harshly on the 5 second refresh). I'd like for it to reverse maybe from the last angle?
I call this widget as this:
SpinnerAnimation(body: Icon(FontAwesomeIcons.earthAmericas, size: 33, color: Colors.white,),),
import 'dart:async';
import 'dart:math' as math;
import 'package:flutter/material.dart';
class SpinnerAnimation extends StatefulWidget {
late final Widget body;
SpinnerAnimation({required this.body});
#override
_SpinnerAnimationState createState() =>
_SpinnerAnimationState(body: this.body);
}
class _SpinnerAnimationState extends State<SpinnerAnimation>
with SingleTickerProviderStateMixin {
late final Widget body;
_SpinnerAnimationState({required this.body});
late AnimationController _controller;
double change_rotation_speed = 1.2;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 10),
vsync: this,
)..repeat();
//timer repeat every 5 seconds:
Timer.periodic(Duration(seconds: 5), (Timer t) => change_rotation_speed = randoDoubleGeneratorWithRange(1.2, 5) );
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
child: body,
builder: (BuildContext context, Widget? child) {
return Transform.rotate(
angle: _controller.value * change_rotation_speed * math.pi,
child: child,
);
},
);
}
double randoDoubleGeneratorWithRange(double min, double max) {
//return a random double value:
var random = new Random();
double result = min + random.nextDouble() * (max - min);
//print(result);
return result;
}
}

You can use RotationTransition and with Animation
.............
late AnimationController _controller;
late Animation<double> animationRotation;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 5),
vsync: this,
)..repeat(revere:true); // set true for reverse animation
animationRotation =
Tween<double>(begin: 0.0, end: 1.0).animate(_controller);
}
........
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
child: body,
builder: (BuildContext context, Widget? child) {
return RotationTransition(
turns: animationRotation,
child: child,
);
},
);
here demo link on Dartpad: https://dartpad.dev/?id=e316af128311c0b42d7aef2fd9e037df

Related

Why are my animations skipping steps sometimes?

I'm trying to animate a property of a CustomPainter widget that get's its values from an item provided by a Riverpod Notifier.
In this broken down example of my real app, I trigger the Notifier by changing the data of the second Item which then should resize the circle in front of the ListTile.
It seems to work for changes where the value increases but when the value decreases, it often jumps over parts of the animation.
I'm not sure if I'm doing the whole animation part right here.
The code is also on Dartpad:
https://dartpad.dev/?id=e3916b47603988efabd7a08712b98287
// ignore_for_file: avoid_print
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(
const ProviderScope(
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Riverpod + animated CustomPainter',
home: const Example3(),
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.orange,
brightness: MediaQueryData.fromWindow(WidgetsBinding.instance.window).platformBrightness,
surface: Colors.deepOrange[600],
),
),
);
}
}
class ItemPainter extends CustomPainter {
final double value;
ItemPainter(this.value);
final itemPaint = Paint()..color = Colors.orange;
#override
void paint(Canvas canvas, Size size) {
// draw a circle with a size depending on the value
double radius = size.width / 10 * value / 2;
canvas.drawCircle(
Offset(
size.width / 2,
size.height / 2,
),
radius,
itemPaint,
);
}
#override
bool shouldRepaint(covariant ItemPainter oldDelegate) => oldDelegate.value != value;
}
CustomPaint itemIcon(double value) {
return CustomPaint(
painter: ItemPainter(value),
size: const Size(40, 40),
);
}
#immutable
class Item {
const Item({required this.id, required this.value});
final String id;
final double value;
}
// notifier that provides a list of items
class ItemsNotifier extends Notifier<List<Item>> {
#override
List<Item> build() {
return [
const Item(id: 'A', value: 1.0),
const Item(id: 'B', value: 5.0),
const Item(id: 'C', value: 10.0),
];
}
void randomize(String id) {
// replace the state with a new list of items where the value is randomized from 0.0 to 10.0
state = [
for (final item in state)
if (item.id == id) Item(id: item.id, value: Random().nextInt(100).toDouble() / 10.0) else item,
];
}
}
class AnimatedItem extends StatefulWidget {
final Item item;
const AnimatedItem(this.item, {super.key});
#override
State<AnimatedItem> createState() => _AnimatedItemState();
}
class _AnimatedItemState extends State<AnimatedItem> with SingleTickerProviderStateMixin {
late final AnimationController _animationController;
late Animation<double> animation;
#override
void initState() {
super.initState();
_animationController = AnimationController(
value: widget.item.value,
vsync: this,
duration: const Duration(milliseconds: 3000),
);
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
void didUpdateWidget(AnimatedItem oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.item.value != widget.item.value) {
print('didUpdateWidget: ${oldWidget.item.value} -> ${widget.item.value}');
_animationController.value = oldWidget.item.value / 10;
_animationController.animateTo(widget.item.value / 10);
}
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return itemIcon((widget.item.value * _animationController.value));
},
);
}
}
final itemsProvider = NotifierProvider<ItemsNotifier, List<Item>>(() => ItemsNotifier());
class Example3 extends ConsumerWidget {
const Example3({super.key});
#override
Widget build(BuildContext context, WidgetRef ref) {
final items = ref.watch(itemsProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Animated CustomPainter Problem'),
),
// iterate over the item list in ItemsNotifier
body: ListView.separated(
separatorBuilder: (context, index) => const Divider(),
itemCount: items.length,
itemBuilder: (context, index) {
final item = items.elementAt(index);
return ListTile(
key: Key(item.id),
leading: AnimatedItem(item),
title: Text('${item.value}'),
);
},
),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () {
ref.read(itemsProvider.notifier).randomize('B'); // randomize the value of the second item
},
child: const Icon(Icons.change_circle),
),
],
),
);
}
}
Your issues lie completely in your implementation of the AnimationController. I don't really understand your intent with the original code, but the reason it jumped was because your were doing widget.item.value * _animationController.value in the build function. When you updated your item's value, it suddenly changed widget.item.value, creating the jump, then animating a small change with the AnimationController.
This code will work:
class _AnimatedItemState extends State<AnimatedItem> with SingleTickerProviderStateMixin {
late final AnimationController _animationController;
late Animation<double> animation;
#override
void initState() {
super.initState();
_animationController = AnimationController(
value: widget.item.value,
vsync: this,
duration: const Duration(milliseconds: 3000),
lowerBound: 0.0,
upperBound: 10.0
);
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
void didUpdateWidget(AnimatedItem oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.item.value != widget.item.value) {
print('didUpdateWidget: ${oldWidget.item.value} -> ${widget.item.value}');
_animationController.animateTo(widget.item.value);
}
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return itemIcon(_animationController.value);
},
);
}
}
This code adjusts the bounds of your AnimationController to accommodate the range of values you want to animate and only uses _animationController.value in build. I also removed a redundant line from didUpdateWidget, but that had no effect on functionality.

Flutter GetX: Animation with GetxController

I'm trying GetX for my new project and tried to use AnimationController which is inspired this comment. Everything works fine with default value for Tween -> blur & spread & AnimationController -> duration.
What I'm doing:
1: Created an widget with corresponding controller (controller is binded through initialBinding of GetMaterialApp).
GetMaterialApp(
...
initialBinding: AnimationBinding()
...
);
class AnimationBinding extends Bindings {
#override
void dependencies() {
Get.lazyPut<AnimationController>(
() => AnimationController(),
);
}
}
class CustomAnimatedWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return GetBuilder<LogoAnimationController>(
builder: (_) {
return Container(
width: 150,
height: 150,
child: Text('for demo only, originally its not text widget'),
...
remaining code that uses `_.animation.value`
...
),
);
},
);
}
}
class AnimationController extends GetxController with SingleGetTickerProviderMixin {
Animation animation;
AnimationController _animationController;
#override
void onInit() {
super.onInit();
_animationController = AnimationController(
vsync: this, duration: Duration(seconds: 2));
_animationController.repeat(reverse: true);
animation = Tween(begin: 2.0, end: 15.0)
.animate(_animationController)
..addListener(() => update());
}
#override
void onReady() {
super.onReady();
}
#override
void onClose() {
super.onClose();
_animationController.dispose();
}
}
2: Used this widget inside view.
child: CustomAnimatedWidget();
Till now, everything is working fine. But I want to update blur, spread & duration, with:
updating CustomAnimatedWidget:
final double blur;
final double spread;
final int duration;
CustomAnimatedWidget({this.blur, this.spread, this.duration});
...
builder: (_) => ...
...
initState: (s) {
_.blur.value = blur;
_.spread.value = spread;
_.duration.value = duration;
},
...
updating AnimationController:
Rx<double> blur = 2.0.obs;
Rx<double> spread = 15.0.obs;
Rx<int> duration = 2.obs;
and using/calling the CustomAnimationWidget using:
child: CustomAnimatedWidget(duration: 4, blur: 2, spread: 10);
but don't know how to use it with _animationController & _animation because they are called in onInit.
Please share your solutions, thanks.
I Extended GetxController with GetTickerProviderStateMixin and then make a AnimationController and CurvedAnimation by code below:
late final AnimationController _controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..repeat(reverse: false);
late final Animation<double> animation = CurvedAnimation(
parent: _controller,
curve: Curves.ease,
);
After that i just created object of my CustomGetxController like this final _customController = Get.put(CustomGetxController()); then i called it like this:
FadeTransition(
opacity: driverController.animation,
child:const Text('My Flashing Text')
),

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,
),
)

Animate a color periodically in flutter

I have a container widget that I try to animate from Colors.blue to Colors.blueGrey and back periodically every 2 seconds.
How can I most easily tackle this in Flutter?
You can use an infinite while loop, don't think this is the best way of doing this, but it gets the job done.
I have a Color Changing class
class ColorChanger extends StatefulWidget {
#override
_ColorChangerState createState() => _ColorChangerState();
}
class _ColorChangerState extends State<ColorChanger>
with SingleTickerProviderStateMixin {
AnimationController _animationController;
Animation _colorTween;
#override
void initState() {
_animationController = AnimationController(
vsync: this, duration: Duration(milliseconds: 1999));
_colorTween = ColorTween(begin: Colors.blue, end: Colors.blueGrey)
.animate(_animationController);
changeColors();
super.initState();
}
Future changeColors() async {
while (true) {
await new Future.delayed(const Duration(seconds: 2), () {
if (_animationController.status == AnimationStatus.completed) {
_animationController.reverse();
} else {
_animationController.forward();
}
});
}
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _colorTween,
builder: (context, child) => Container(
color: _colorTween.value,
),
);
}
}
This is a rough example, but should lead you in the right direction.
Please see ColorTween Class
I would suggest using the AnimatedContainer. This widget allows you to build it with a particular atribute like color and when you rebuild it with a different value it performs linear interpolation between those values.
#override
Widget build(BuildContext context) {
return AnimatedContainer(
width: 100,
height: 100,
duration: _animationDuration,
color: _color,
);
}
Then you just have to rebuild the widget with a different color:
void _changeColor() {
final newColor = _color == Colors.blue ? Colors.blueGrey : Colors.blue;
setState(() {
_color = newColor;
});
}
The make it periodically I would use a timer class:
_timer = Timer.periodic(_animationDuration, (timer) => _changeColor());
The whole code:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class AnimationTest extends StatefulWidget {
#override
_AnimationTestState createState() => _AnimationTestState();
}
class _AnimationTestState extends State<AnimationTest> {
final _animationDuration = Duration(seconds: 2);
Timer _timer;
Color _color;
#override
void initState() {
super.initState();
_timer = Timer.periodic(_animationDuration, (timer) => _changeColor());
_color = Colors.blue;
}
void _changeColor() {
final newColor = _color == Colors.blue ? Colors.blueGrey : Colors.blue;
setState(() {
_color = newColor;
});
}
#override
Widget build(BuildContext context) {
return AnimatedContainer(
width: 100,
height: 100,
duration: _animationDuration,
color: _color,
);
}
#override
void dispose() {
super.dispose();
_timer.cancel();
}
}

How do i access an inner widget's animation controller (or state) from outer widget?

So I’ve been experimenting with Flutter & Dart for the past few days.
I’m stuck on this one for over a day now, so I’m here.
So I have the AlarmScreen, and we have 2 objects inside it. One is the DraggableMoonWidget, and the other is the RisingSunWidget.
Currently, the RisingSunWidget animates onto the screen from the bottom, while the DraggableMoonWidget is draggable by touch.
What I want to achieve, is that when the RisingSunWidget’s animation would stop and change when the DraggableMoonWidget is being dragged. So I have the MoonDragListener in place and working, but I still can’t figure it out. (currently calls the listener back to the AlarmScreen, but then what?)
I have tried a whole bunch of methods to do that, all deleted since then, as not a single one worked.
TLDR
How do I control the RisingSunWidget’s animation controller when the user touches the DraggableMoonWidget, for example, I want to stop the controller and animate it to a different point.
What is the best approach in dart/flutter?
AlarmScreen
import 'package:flutter/widgets.dart';
import 'package:moonworshiper_app/backgrounds.dart';
import 'package:moonworshiper_app/ui/alarm/moon_draggable.dart';
import 'package:moonworshiper_app/ui/alarm/rising_sun.dart';
class AlarmScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return new _AlarmScreenState();
}
}
class _AlarmScreenState extends State<AlarmScreen> {
bool _moonWasTouched = false;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return new Stack(
children: <Widget>[
new DraggableMoonWidget(new MoonDragListener(this)),
new LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return new RisingSunWidget(constraints.heightConstraints().maxHeight, _moonWasTouched);
})
],
);
}
void _refreshSun() {
setState(() {
_moonWasTouched = true;
});
}
}
class MoonDragListener {
_AlarmScreenState state;
MoonDragListener(this.state);
void onMoonDragStarted() {
state._refreshSun();
}
}
DraggableMoonWidget
import 'package:flutter/widgets.dart';
import 'package:moonworshiper_app/ui/alarm/alarm_screen.dart';
class DraggableMoonWidget extends StatefulWidget {
final MoonDragListener moonStartListener;
DraggableMoonWidget(this.moonStartListener);
State<StatefulWidget> createState() => new _DraggableMoonState();
}
class _DraggableMoonState extends State<DraggableMoonWidget>
with TickerProviderStateMixin {
final moonDragTween = new Tween<Offset>(
begin: new Offset(0.0, -0.5),
end: new Offset(0.0, 0.5),
);
var moonAnimListener;
AnimationController _animationController;
Animation<Offset> _dragAnimation;
AnimationController _dragAnimationController;
bool isFirstDraw = true;
#override
initState() {
super.initState();
_animationController = new AnimationController(
vsync: this,
duration: const Duration(milliseconds: 3000),
);
_dragAnimationController = new AnimationController(vsync: this);
moonAnimListener = (AnimationStatus status) {
if (status == AnimationStatus.dismissed) {
_animationController.forward();
} else if (status == AnimationStatus.completed) {
_animationController.reverse();
} else if (status == AnimationStatus.forward) {}
};
_dragAnimation = moonDragTween.animate(new CurvedAnimation(
parent: _dragAnimationController,
curve: Curves.easeInOut,
reverseCurve: Curves.easeInOut));
_dragAnimationController.animateTo(0.5, duration: new Duration());
_animationController.addStatusListener(moonAnimListener);
_animationController.forward();
}
#override
Widget build(BuildContext context) {
return new Container(
child: new Center(
child: new SlideTransition(
position: _dragAnimation,
child: new GestureDetector(
child: new Image.asset(
"assets/moon.png",
width: 280.0,
height: 280.0,
),
onVerticalDragStart: (DragStartDetails details) {
print("start:" + details.globalPosition.toString());
_animationController.removeStatusListener(moonAnimListener);
_animationController.stop();
_dragStartDetails = details;
_dragAnimationController.animateTo(0.5,
duration: new Duration(milliseconds: 50));
if (isFirstDraw) {
isFirstDraw = false;
widget.moonStartListener.onMoonDragStarted();
}
},
),
),
),
// margin: new EdgeInsets.only(top: 48.0),
);
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
}
RisingSunWidget
import 'package:flutter/widgets.dart';
class RisingSunWidget extends StatefulWidget {
// needed to calculate the offset map
final double screenHeight;
// that's how we know if the use touched the moon
final bool moonWasTouched;
RisingSunWidget(this.screenHeight, this.moonWasTouched);
#override
State<StatefulWidget> createState() {
return new RisingSunState();
}
}
class RisingSunState extends State<RisingSunWidget> with TickerProviderStateMixin {
AnimationController _animationController;
Animation<Offset> _sunAnimation;
final double sunSize = 320.0;
#override
initState() {
super.initState();
_animationController = new AnimationController(
vsync: this,
duration: const Duration(milliseconds: 6000),
);
// how many suns fit in the height of our screen
assert(widget.screenHeight > sunSize);
double sunsInHeight = widget.screenHeight / sunSize;
print(sunsInHeight.toString() + " suns could fit on the user's screen");
var sunsPlusMargins = sunsInHeight + 1; // required margins
final moonTween = new Tween<Offset>(
begin: new Offset(0.0, -0.5 * sunsPlusMargins),
end: new Offset(0.0, 0.5 * sunsPlusMargins), //move by 8% of height max
);
_sunAnimation = moonTween.animate(new CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
reverseCurve: Curves.easeInOut,
));
if (widget.moonWasTouched) {
_animationController.stop();
_animationController.animateTo(0.68,
duration: new Duration(milliseconds: 2000));
} else {
_animationController.animateTo(0.88,
duration: new Duration(milliseconds: 0));
_animationController.animateTo(0.75,
duration: new Duration(milliseconds: 15000));
}
}
#override
Widget build(BuildContext context) {
return new Center(
child: new SlideTransition(
position: _sunAnimation,
child: new Image.asset(
"assets/sun.png",
width: sunSize,
height: sunSize,
),
),
);
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
}
2 possibilities :
Use the BuildContext context that is provided in your build method. BuildContext has a few methods get the closest parent of a specific type.
Pass a key attribute to the desired widget.
GlobalKey to be exact.
GlobalKey allows you directly access to a Widget or it's state.