Flutter Reworked question: Issue sharing states between widget with button and widget with countdown timer
You can copy paste run full code below
Step 1: You can put controller
inside CountDownTimerState
Step 2: Use GlobalKey
CountDownTimer(key: _key)
Step 3: Call function start()
inside _CountDownTimerState
with _key.currentState
goSelected: () {
setState(() {
...
_controller.reverse(from: 10.0); // start the countdown animation
final _CountDownTimerState _state = _key.currentState;
_state.start();
...
class _CountDownTimerState extends State<CountDownTimer>
with TickerProviderStateMixin {
AnimationController _controller;
@override
void initState() {
_controller =
AnimationController(vsync: this, duration: Duration(seconds: 7));
super
.initState(); // here I have some difference to Andrey's answer because I do not use Tween
}
...
void start() {
setState(() {
_controller.reverse(from: 1.0);
});
}
working demo
full code
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'dart:math' as math;
class MainPage extends StatefulWidget {
MainPage({Key key, this.title}) : super(key: key);
final String title;
@override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> with TickerProviderStateMixin {
AnimationController _controller;
var answer = "0", correctAnswer = true, result = 0;
GlobalKey _key = GlobalKey();
@override
void initState() {
super.initState();
_controller =
AnimationController(vsync: this, duration: Duration(seconds: 7));
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
//navigationBar: CupertinoNavigationBar(),
child: SafeArea(
child: Container(
color: Colors.blue,
child: Column(children: <Widget>[
CreateKeypad(
// creates a keypad with a go button. when go is clicked, countdown shall start
prevInput: int.parse((answer != null ? answer : "0")),
updtedInput: (int val) {
setState(() => answer = val.toString());
},
goSelected: () {
setState(() {
if (answer == result.toString()) {
correctAnswer = true;
}
/*final problem = createProblem();
result = problem.result;*/
});
print("go");
_controller.reverse(from: 10.0); // start the countdown animation
final _CountDownTimerState _state = _key.currentState;
_state.start();
/* Future.delayed(
const Duration(
milliseconds: 300,
),
() => setState(() => correctAnswer = true));*/
},
),
Container(
height: 400,
width: 400,
child: CountDownTimer(key: _key)), // show countdown timer
]),
),
));
}
}
// CREATE KEYPAD - all keys but "1! and "go" removed
class CreateKeypad extends StatelessWidget {
final int prevInput;
final VoidCallback goSelected;
final Function(int) updtedInput;
CreateKeypad(
{@required this.prevInput, @required this.updtedInput, this.goSelected});
@override
Widget build(BuildContext context) {
return Row(children: <Widget>[
Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(2.0),
child: SizedBox(
width: 80.0,
height: 80.0,
child: CupertinoButton(
child:
Text("1", style: TextStyle(color: CupertinoColors.black)),
onPressed: () {
updtedInput(1);
},
),
),
),
Padding(
padding: const EdgeInsets.all(2.0),
child: SizedBox(
width: 80.0,
height: 80.0,
child: CupertinoButton(
child:
Text("Go!", style: TextStyle(color: CupertinoColors.black)),
onPressed: () => goSelected(),
),
),
),
],
),
]);
}
}
// CREATE COUNTDOWN https://medium.com/flutterdevs/creating-a-countdown-timer-using-animation-in-flutter-2d56d4f3f5f1
class CountDownTimer extends StatefulWidget {
CountDownTimer({Key key}) : super(key: key);
//final AnimationController _controller;
@override
_CountDownTimerState createState() => _CountDownTimerState();
}
class _CountDownTimerState extends State<CountDownTimer>
with TickerProviderStateMixin {
AnimationController _controller;
@override
void initState() {
_controller =
AnimationController(vsync: this, duration: Duration(seconds: 7));
super
.initState(); // here I have some difference to Andrey's answer because I do not use Tween
}
String get timerString {
Duration duration = _controller.duration * _controller.value;
return '${duration.inMinutes}:${(duration.inSeconds % 60).toString().padLeft(2, '0')}';
}
void start() {
setState(() {
_controller.reverse(from: 1.0);
});
}
@override
Widget build(BuildContext context) {
return Container(
color: Colors.green,
child: AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget child) {
return CustomPaint(
painter: CustomTimerPainter(
// this draws a white donut and a red diminishing arc on top
animation: _controller,
backgroundColor: Colors.green,
color: Colors.red,
));
},
),
);
}
}
class CustomTimerPainter extends CustomPainter {
CustomTimerPainter({
this.animation,
this.backgroundColor,
this.color,
}) : super(repaint: animation);
final Animation<double> animation;
final Color backgroundColor, color;
@override
void paint(Canvas canvas, Size size) {
Paint paint = Paint()
..color = backgroundColor
..strokeWidth = 10.0
..strokeCap = StrokeCap.butt
..style = PaintingStyle.stroke;
canvas.drawCircle(size.center(Offset.zero), size.width / 2.0, paint);
paint.color = color;
double progress = (1.0 - animation.value) * 2 * math.pi;
//print("progress ${progress}");
canvas.drawArc(Offset.zero & size, math.pi * 1.5, -progress, false, paint);
}
@override
bool shouldRepaint(CustomTimerPainter old) {
//print(animation.value);
return true;
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MainPage(),
);
}
}
w461
Programmed C long time ago, did some minor VBA in between and currently I am trying to create some basic home schooling apps in flutter and/or swift - just in case...
Updated on December 23, 2022Comments
-
w461 over 1 year
I am trying since some days to connect an animated stateful child widget with a countdown timer to the parent stateful widget with the user interaction. I found this answer from Andrey on a similar question (using Tween which I do not) that already helped a lot, but I still don't get it to work. My assumption is, the child's initState could be the reason. The timer's code comes from here.
I have removed quite some code including some referenced functions/classes. This should provide a clearer picture on the logic:
- In MainPageState I declare and init the _controller of the animation
- In MainPageState I call the stateless widget CreateKeypad hosting among others the "go" key
- When go is clicked, this event is returned to MainPageState and
_controller.reverse(from: 1.0);
executed - In MainPageState I call the stateful widget CountDownTimer to render the timer
- In _CountDownTimerState I am not sure if my initState is correct
- In _CountDownTimerState I build the animation with CustomTimerPainter from the timer code source
The animation shall render a white donut and a red, diminishing arc on top. However, I only see the white donut, not the red timer's arc. Any hint is highly appreciated.
class MainPage extends StatefulWidget { MainPage({Key key, this.title}) : super(key: key); final String title; @override _MainPageState createState() => _MainPageState(); } class _MainPageState extends State<MainPage> with TickerProviderStateMixin { AnimationController _controller; var answer="0", correctAnswer = true, result = 0; @override void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: Duration(seconds: 7)); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( ), child: SafeArea( child: Container( child: Column( children: <Widget>[ CreateKeypad( // creates a keypad with a go button. when go is clicked, countdown shall start prevInput: int.parse((answer != null ? answer : "0")), updtedInput: (int val) { setState(() => answer = val.toString()); }, goSelected: () { setState(() { if (answer == result.toString()) { correctAnswer = true; } final problem = createProblem(); result = problem.result; }); _controller.reverse(from: 1.0); // start the countdown animation Future.delayed(const Duration(milliseconds: 300,), () => setState(() => correctAnswer = true)); }, ), CountDownTimer(_controller), // show countdown timer ] ), ), ) ); } } // CREATE KEYPAD - all keys but "1! and "go" removed class CreateKeypad extends StatelessWidget { final int prevInput; final VoidCallback goSelected; final Function(int) updtedInput; CreateKeypad({@required this.prevInput, @required this.updtedInput, this.goSelected}); @override Widget build(BuildContext context) { return Row( children: <Widget> [ Column( children: <Widget>[ Padding( padding: const EdgeInsets.all(2.0), child: SizedBox( width: 80.0, height: 80.0, child: CupertinoButton( child: Text("1", style: TextStyle(color: CupertinoColors.black)), onPressed: () { updtedInput(1); }, ), ), ), Padding( padding: const EdgeInsets.all(2.0), child: SizedBox( width: 80.0, height: 80.0, child: CupertinoButton( child: Text("Go!", style: TextStyle(color: CupertinoColors.black)), onPressed: () => goSelected(), ), ), ), ], ), ] ); } } // CREATE COUNTDOWN https://medium.com/flutterdevs/creating-a-countdown-timer-using-animation-in-flutter-2d56d4f3f5f1 class CountDownTimer extends StatefulWidget { CountDownTimer(this._controller); final AnimationController _controller; @override _CountDownTimerState createState() => _CountDownTimerState(); } class _CountDownTimerState extends State<CountDownTimer> with TickerProviderStateMixin { @override void initState() { super.initState(); // here I have some difference to Andrey's answer because I do not use Tween } String get timerString { Duration duration = widget._controller.duration * widget._controller.value; return '${duration.inMinutes}:${(duration.inSeconds % 60) .toString() .padLeft(2, '0')}'; } @override Widget build(BuildContext context) { return Container( child: AnimatedBuilder( animation: widget._controller, builder: (BuildContext context, Widget child) { return CustomPaint( painter: CustomTimerPainter( // this draws a white donut and a red diminishing arc on top animation: widget._controller, backgroundColor: Colors.white, color: Colors.red, )); }, ), ); } }
-
w461 over 3 yearsThank you so much for your efforts! While your demo works perfectly fine and meanwhile I have also found my last error in my adaption (I copy pasted the relevant lines from your code), it still doesn't work in my context. Still no red arc. Could you please explain your solution a bit to help me finding the issue?
-
w461 over 3 yearsIn the MainPage state, you create a global key _key. Then, I guess, you copy the state of CountDownTimer into _state with
final _CountDownTimerState _state = _key.currentState;
. Then it appears that you can launch the function start within _CountDownTimerState from MainPageState with ` _state.start();` This functions sets within _CountDownTimerState the local animation controller _controller to reverse from 1. Is this the flow? -
w461 over 3 yearsSTUPID ME: Wrapping a container with width and height around CountDownTimer(key: _key) as you did, chunhunghan, did the trick. I guess, I painted my arc somewhere else in space... Everything fine now - at least when I finished positioning the arc