【问题标题】:Flutter Reworked question: Issue sharing states between widget with button and widget with countdown timerFlutter Reworked 问题:问题在带有按钮的小部件和带有倒数计时器的小部件之间共享状态
【发布时间】:2020-12-17 03:29:21
【问题描述】:

几天以来,我一直在尝试将带有倒数计时器的动画有状态子小部件连接到具有用户交互的父有状态小部件。我在 Andrey 的一个类似问题上找到了这个answer(使用我没有使用的 Tween),这已经有很大帮助,但我仍然无法让它发挥作用。我的假设是,孩子的 initState 可能是原因。定时器的代码来自here

我已经删除了相当多的代码,包括一些引用的函数/类。这应该提供更清晰的逻辑图片:

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

【问题讨论】:

    标签: flutter dart state parent-child


    【解决方案1】:

    您可以在下面复制粘贴运行完整代码
    第一步:你可以把controller放在CountDownTimerState里面
    第二步:使用GlobalKey

     CountDownTimer(key: _key)
    

    第 3 步:在 _CountDownTimerState 中调用函数 start()_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);
        });
      }
    

    工作演示

    完整代码

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

    【讨论】:

    • 非常感谢您的努力!虽然您的演示工作得很好,同时我也发现了我在改编中的最后一个错误(我从您的代码中复制粘贴了相关行),但它在我的上下文中仍然不起作用。还是没有红弧。您能否解释一下您的解决方案以帮助我找到问题?
    • 在 MainPage 状态下,您创建一个全局键 _key。然后,我猜,你将 CountDownTimer 的状态复制到带有final _CountDownTimerState _state = _key.currentState; 的_state 中。然后看来您可以使用`_state.start();`从MainPageState 启动_CountDownTimerState 内的函数启动此函数在_CountDownTimerState 内设置本地动画控制器_controller 从1 反转。这是流程吗?
    • 愚蠢的我:像你一样在 CountDownTimer(key: _key) 周围包裹一个宽度和高度的容器,chunhunghan,成功了。我想,我在太空的其他地方画了我的弧线......现在一切都很好 - 至少当我完成定位弧线时
    猜你喜欢
    • 1970-01-01
    • 2021-05-19
    • 1970-01-01
    • 2017-12-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-23
    • 1970-01-01
    相关资源
    最近更新 更多