I'm very new to Flutter and I'm trying to understand animations.
This widget will slide up from bottom to center when rendered.
It seems to work, but I'm uncertain if this is the way to do it.
So what I want is that every time this Text widget is put on the screen it should animate from bottom to center. I did that by calling a function from WidgetsBinding.instance.addPostFrameCallback that changes the state in the initState .
Is there a better, cleaner way to this?
class AnimatedCenterText extends StatefulWidget {
final String text;
AnimatedCenterText(this.text);
#override
_AnimatedCenterTextState createState() => _AnimatedCenterTextState();
}
class _AnimatedCenterTextState extends State<AnimatedCenterText> {
AlignmentGeometry _alignment = Alignment.bottomCenter;
void _changeAlignment() {
setState(() {
_alignment = Alignment.center;
});
}
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_changeAlignment();
});
}
#override
Widget build(BuildContext context) {
return AnimatedAlign(
alignment: _alignment,
duration: Duration(milliseconds: 300),
curve: Curves.easeInCubic,
child: Text(
widget.text,
style: TextStyle(fontSize: 28),
),
);
}
}
Related
I am new to flutter.
I was trying to make a custom widget that uses Bézier curve to draw a gooey circle, which changes its position based on the value of the slider.
I have made a gif to show what I was up to.GooeyCircle
The problem is, I need to change the gooey circle's position(progress) based on _sliderValue, but it seems that the progress in the GooeyCircleWidget stays the initial value and never changes whenever the slider goes. I want it to be passed into the widget dynamically, could anyone please help me find out what I should do? Thanks!
GooeyCircleWidget(
progress: _sliderValue,
color: Colors.blue,
),
class GooeyCircleWidget extends StatefulWidget {
Color color;
double progress ;
_GooeyCircleWcoloridgetState createState() => _GooeyCircleWidgetState(
progress: progress,
color:color,
);
GooeyCircleWidget({
#required this.progress,
this.color
});å
}
class _GooeyCircleWidgetState extends State<GooeyCircleWidget>
with SingleTickerProviderStateMixin {
final double progress;
AnimationController controller;
final Color color;
_GooeyCircleWidgetState(
{#required this.progress,
this.color});
#override
void initState() {
super.initState();
controller = AnimationController(vsync: this);
}
#override
Widget build(BuildContext context) {
print(progress);
// TODO: implement build
return CustomPaint(
painter: AnimatedCirclePainter(
progress: progress,
color: color,
),
size: Size(1080, 200),
);
}
}
Don't pass the variables from GooeyCircleWidget into the constructor of _GooeyCircleWidgetState. Instead use widget.
Like so:
class GooeyCircleWidget extends StatefulWidget {
Color color;
double progress ;
#override
_GooeyCircleWidgetState createState() => _GooeyCircleWidgetState();
GooeyCircleWidget({
#required this.progress,
this.color,
});
}
class _GooeyCircleWidgetState extends State<GooeyCircleWidget>
with SingleTickerProviderStateMixin {
AnimationController controller;
#override
void initState() {
super.initState();
controller = AnimationController(vsync: this);
}
#override
Widget build(BuildContext context) {
print(widget.progress);
// TODO: implement build
return CustomPaint(
painter: AnimatedCirclePainter(
progress: widget.progress,
color: widget.color
),
size: Size(1080, 200),
);
}
}
this below simple widget is used in my application for making simple marquee text into another widgets for Text, in that when text length is too short i get this error:
ScrollController not attached to any scroll views error
my problem is know how can i calculate text length in dynamic with and avoid throwing this error?
for example:
MarqueeWidget(
direction: Axis.horizontal,
child: Text(
'$parsedString', //-> text length is not known
style: AppTheme.of(context).caption(
),
),
and this is MarqueeWidget which i used from it
class MarqueeWidget extends StatefulWidget {
final Widget child;
final Axis direction;
final Duration animationDuration, backDuration, pauseDuration;
MarqueeWidget({
#required this.child,
this.direction: Axis.horizontal,
this.animationDuration: const Duration(milliseconds: 7000),
this.backDuration: const Duration(milliseconds: 2000),
this.pauseDuration: const Duration(milliseconds: 2000),
});
#override
_MarqueeWidgetState createState() => _MarqueeWidgetState();
}
class _MarqueeWidgetState extends State<MarqueeWidget> {
ScrollController scrollController = ScrollController();
#override
void initState() {
try{
scroll();
}catch(error){
}
super.initState();
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: widget.child,
scrollDirection: widget.direction,
controller: scrollController,
);
}
void scroll() async {
while (true) {
await Future.delayed(widget.pauseDuration);
await scrollController.animateTo(
scrollController.position.maxScrollExtent,
duration: widget.animationDuration,
curve: Curves.easeIn);
await Future.delayed(widget.pauseDuration);
await scrollController.animateTo(0.0,
duration: widget.backDuration, curve: Curves.easeOut);
}
}
}
Just change your code as follow,
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) {
try {
scroll();
} catch (error){
// log error
}
});
}
WidgetsBinding.instance.addPostFrameCallback() method gets called once the build() method gets rendered.
The error in your case is you are calling scroll() method which tries to scroll using scrollController which might be called before the build method gets executed.
I've created a screen in Flutter that displays a countdown timer. I'm able to play, pause and restart the timer, but I am trying to figure out how to perform an action when the timer reaches 0 (for example, restart itself).
As the dart file is fairly lengthy, I'm just copying below what I believe to be the relevant portions here. But I can add more if needed.
First I create a widget/class for the countdown timer:
class Countdown extends AnimatedWidget {
Countdown({ Key key, this.animation }) : super(key: key, listenable: animation);
Animation<int> animation;
#override
build(BuildContext context){
return Text(
animation.value.toString(),
style: TextStyle(
fontSize: 120,
color: Colors.deepOrange
),
);
}
}
I then have a stateful widget which creates the controller and also imports some data (gameData.countdownClock is the countdown timer's start time, it comes from user input at an earlier screen):
class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
AnimationController _controller;
_GameScreenState(this.gameData);
GameData gameData;
#override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: gameData.countdownClock),
);
}
And then the container that displays the clock:
Container(
child: Countdown(
animation: StepTween(
begin: gameData.countdownClock,
end: 0,
).animate(_controller),
),
),
Do I have to add a listener in that last container? Or somewhere else? (Or something else entirely!)
Any help is appreciated. Thank you
I found the answer on this page:
After complete the widget animation run a function in Flutter
I needed to add .addStatusListener to the animation controller in the initState().
So the new initState() code looks like this:
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: gameData.countdownClock),
);
_controller.addStatusListener((status){
if(status == AnimationStatus.completed){
_controller.reset();
}
}
);
}
Possible value of Animation controller is between 0 to 1.
So I think you have to add listener on gamedata.countDownClock
Please check the below code you might get some idea from it.
import 'dart:async';
import 'package:flutter/material.dart';
class GameScreen extends StatefulWidget {
#override
_GameScreenState createState() => _GameScreenState();
}
class _GameScreenState extends State<GameScreen> with SingleTickerProviderStateMixin {
int countdownClock = 10;
#override
void initState() {
super.initState();
// Your Game Data Counter Change every one Second
const oneSec = const Duration(seconds:1);
new Timer.periodic(oneSec, (Timer t) {
// Restart The Counter Logic
if (countdownClock == 0)
countdownClock = 11;
setState(() { countdownClock = countdownClock - 1; });
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("")),
body: Center(
child: Text('$countdownClock')
),
);
}
}
I was playing around with showModalBottomSheet() in Flutter and I was thinking about changing the default slide from bottom animation. Looking throught flutter documentation I saw that there is in fact a BottomSheet class that accept animation as parameters but, accordingly to the page, showModalBottomSheet() is preferrable.
Is possible to control the animation in some way? I just need to change the default curve and duration.
Thanks
You can use the AnimationController drive method to modify the animation curve, and duration and reverseDuration to set how long the animation will go on. These can be declared on your initState() if you're using a StatefulWidget.
late AnimationController controller;
#override
initState() {
super.initState();
controller = BottomSheet.createAnimationController(this);
// Animation duration for displaying the BottomSheet
controller.duration = const Duration(seconds: 1);
// Animation duration for retracting the BottomSheet
controller.reverseDuration = const Duration(seconds: 1);
// Set animation curve duration for the BottomSheet
controller.drive(CurveTween(curve: Curves.easeIn));
}
Then configure the BottomSheet transtionAnimationController
showModalBottomSheet(
transitionAnimationController: controller,
builder: (BuildContext context) {
return ...
}
)
Here's a sample that you can try out.
import 'package:flutter/material.dart';
class BottomSheetAnimation extends StatefulWidget {
const BottomSheetAnimation({Key? key}) : super(key: key);
#override
_BottomSheetAnimationState createState() => _BottomSheetAnimationState();
}
class _BottomSheetAnimationState extends State<BottomSheetAnimation>
with TickerProviderStateMixin {
late AnimationController controller;
#override
initState() {
super.initState();
controller = BottomSheet.createAnimationController(this);
// Animation duration for displaying the BottomSheet
controller.duration = const Duration(seconds: 1);
// Animation duration for retracting the BottomSheet
controller.reverseDuration = const Duration(seconds: 1);
// Set animation curve duration for the BottomSheet
controller.drive(CurveTween(curve: Curves.easeIn));
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'BottomSheet',
home: Scaffold(
appBar: AppBar(
title: const Text('BottomSheet'),
),
body: Center(
child: TextButton(
child: const Text("Show bottom sheet"),
onPressed: () {
showModalBottomSheet(
context: context,
transitionAnimationController: controller,
builder: (BuildContext context) {
return const SizedBox(
height: 64,
child: Text('Your bottom sheet'),
);
},
);
},
),
),
),
);
}
}
Demo
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,
);
}