Flutter 重做的问题:在带有按钮的小部件和带有倒数计时器的小部件之间发布共享状态

Flutter Reworked question: Issue sharing states between widget with button and widget with countdown timer

几天以来,我一直在尝试将带有倒数计时器的动画有状态子小部件连接到具有用户交互功能的父有状态小部件。我找到了这个 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.

我删除了相当多的代码,包括一些引用的 functions/classes。这应该提供更清晰的逻辑图:

  1. 在 MainPageState 中,我声明并初始化了动画的 _controller
  2. 在 MainPageState 中,我将无状态小部件 CreateKeypad 称为“开始”键等托管
  3. 点击go时,该事件返回MainPageState并_controller.reverse(from: 1.0);执行
  4. 在 MainPageState 中,我调用有状态小部件 CountDownTimer 来呈现计时器
  5. 在 _CountDownTimerState 中我不确定我的 initState 是否正确
  6. 在 _CountDownTimerState 中,我使用 timer code source
  7. 中的 CustomTimerPainter 构建动画

动画应呈现白色甜甜圈和顶部红色递减弧。但是,我只看到白色甜甜圈,没有看到红色计时器的弧线。非常感谢任何提示。

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

您可以复制粘贴运行下面的完整代码
第 1 步:您可以将 controller 放在 CountDownTimerState
中 第 2 步:使用 GlobalKey

 CountDownTimer(key: _key)

第 3 步:使用 _key.currentState

_CountDownTimerState 中调用函数 start()
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);
    });
  }

工作演示

完整代码

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(),
    );
  }
}