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" />
Related
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);
});
}
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')
),
I'd like to create a beginning icon as Icon.add and end icon which is Icon.close in the AnimatedIcon widget. For e.g. their is a prebuilt animation of add_event that corresponds to begin animation = add and end animation = event. I'd like to change the end animation to be Icon.close. It's unclear how to do this as there's no documentation readily available for creating custom animations. The most relevant code I could find is: https://github.com/flutter/flutter/blob/e10df3c1a65f9d7db3fc5340cffef966f7bd40a6/packages/flutter/lib/src/material/animated_icons/data/add_event.g.dart. I believe I should use vitool. How can I go about creating new animations?
Yes, friend, you need to create your own animation
I have written code for the situation that you talked about
I used to Transform widget , and AnimationController
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
AnimationController animatedController;
double _angle = 0;
#override
void initState() {
animatedController =
AnimationController(vsync: this, duration: Duration(milliseconds: 300));
animatedController.addListener(() {
setState(() {
_angle = animatedController.value * 45 / 360 * pi * 2;
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: InkResponse(
onTap: () {
if (animatedController.status == AnimationStatus.completed)
animatedController.reverse();
else if (animatedController.status == AnimationStatus.dismissed)
animatedController.forward();
},
child: Transform.rotate(
angle: _angle,
child: Icon(
Icons.add,
size: 50,
),
),
),
)));
}
}
If you need to customize more than this, Take a look at the AnimatedContainer
So I had some free time and this is something many people might want to use at some point as we do not have much options for AnimatedIcons that are already given to us.
So I went ahead and built this small package and uploaded it on pub dart that solves what you are looking for.
With this package you can animate any two icons.
Check on pub dart animate_icons
Simply add the package into pubspec.yaml like this:
animate_icons:
Then use this Widget like this:
AnimateIcons(
startIcon: Icons.add,
endIcon: Icons.close,
size: 60.0,
onStartIconPress: () {
print("Clicked on Add Icon");
},
onEndIconPress: () {
print("Clicked on Close Icon");
},
duration: Duration(milliseconds: 500),
color: Theme.of(context).primaryColor,
clockwise: false,
),
if the simple transition between the two icons is enough, then simple_animated_icon package might be useful.
class AnimatedIconButton extends StatefulWidget {
#override
_AnimatedIconButtonState createState() => _AnimatedIconButtonState();
}
class _AnimatedIconButtonState extends State<AnimatedIconButton>
with SingleTickerProviderStateMixin {
bool _isOpened = false;
AnimationController _animationController;
Animation<double> _progress;
#override
void initState() {
super.initState();
_animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 300))
..addListener(() {
setState(() {});
});
_progress =
Tween<double>(begin: 0.0, end: 1.0).animate(_animationController);
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
void animate() {
if (_isOpened) {
_animationController.reverse();
} else {
_animationController.forward();
}
setState(() {
_isOpened = !_isOpened;
});
}
#override
Widget build(BuildContext context) {
return IconButton(
onPressed: animate,
icon: SimpleAnimatedIcon(
startIcon: Icons.add,
endIcon: Icons.close,
progress: _progress,
));
}
}
I have a custom widget that has normal / animated state. Sometimes I want to be it animated, and sometimes static.
I have made a simple test project to demonstrate my problem: test page contains my custom widget (ScoreBoard) and 2 buttons to start / stop animating scoreboard. My problem, that ScoreBoard is not animated, even if I start animation.
Here is my code:
TestPage:
class TestPage extends StatefulWidget {
#override
_TestPageState createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
bool _animate;
#override
void initState() {
_animate = false;
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
ScoreBoard(
text: "Hello, world!",
animate: _animate,
),
FlatButton(
child: Text("START animation"),
onPressed: () {
setState(() {
_animate = true;
});
},
),
FlatButton(
child: Text("STOP animation"),
onPressed: () {
setState(() {
_animate = false;
});
},
),
],
),
);
}
}
ScoreBoard widget:
class ScoreBoard extends StatefulWidget {
final String text;
final bool animate;
const ScoreBoard({Key key, this.text, this.animate}) : super(key: key);
#override
_ScoreBoardState createState() => _ScoreBoardState();
}
class _ScoreBoardState extends State<ScoreBoard>
with SingleTickerProviderStateMixin {
AnimationController _controller;
#override
void initState() {
super.initState();
_controller = new AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
)..forward();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return widget.animate
? ScaleTransition(
child:
Text(widget.text, style: Theme.of(context).textTheme.display1),
scale: new CurvedAnimation(
parent: _controller,
curve: Curves.easeIn,
),
)
: Container(
child:
Text(widget.text, style: Theme.of(context).textTheme.display1),
);
}
}
Would you be so kind to help me? Thanks in advance!
Answer
If you initialize an AnimationController widget and do not specify arguments for lowerBound and upperBound (which is the case here), your animation is going to start by default with lowerBound 0.0.
AnimationController({double value, Duration duration, String debugLabel, double lowerBound: 0.0, double upperBound: 1.0, AnimationBehavior animationBehavior: AnimationBehavior.normal, #required TickerProvider vsync })
Creates an animation controller. [...]
https://docs.flutter.io/flutter/animation/AnimationController-class.html
If you initialize the state of your widget ScoreBoard, the method forward gets called only once during the whole lifetime of your app. The method forward makes that your animation increases from the lowerBound (0.0) to the upperBound (1.0) within 1 second.
Starts running this animation forwards (towards the end).
https://docs.flutter.io/flutter/animation/AnimationController/forward.html
In our case, there is no way back once the method forward got called. We are only able to stop the animation.
Press Ctrl + F5 for a full restart of the app to see the animation.
To make it even clearer, change the duration of your animation to 10 seconds.
Btw. since Dart 2 you do not need to use the new keyword.
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 10),
vsync: this,
)..forward();
}
Example
To see what happens you could add this to your build method:
#override
Widget build(BuildContext context) {
// Execute 'reverse' if the animation is completed
if (_controller.isCompleted)
_controller.reverse();
else if (_controller.isDismissed)
_controller.forward();
// ...
... and do not call the method forward in the initState method:
#override
void initState() {
super.initState();
_controller = new AnimationController(
duration: const Duration(seconds: 10),
vsync: this,
);
}
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.