【问题标题】:Flutter Countdown Timer for Chess App ( stuck in the logic)国际象棋应用程序的 Flutter 倒数计时器(卡在逻辑中)
【发布时间】:2020-06-26 14:56:28
【问题描述】:

所以从过去的几天开始,我一直在试图绕过这个逻辑循环,并尝试了我所能做的一切,最后决定在这里发布(因此我在这里首次亮相)。我被卡住了,感谢所有帮助。谢谢!

逻辑:在应用中有 2 个container 显示白方和黑方的时间,从给定的五个选项中选择 -> 5,10,15,30,60,然后更新是在这些容器中显示的时间,为此使用了 provider 包。

现在我还添加了一个名为“switch”的Raised button,按下它时应该:

  1. 开始是白色的倒计时
  2. 然后如果再次按下应该停止白方的计时器,然后启动黑方的倒计时
  3. 然后如果再次按下应该停止黑色的计时器,然后启动白色的计时器。
  4. 然后重复直到计时器用完并且两人中的一人被宣布为获胜者。

问题: 因此,到目前为止,我编写的代码是,当按下“开关”时,它会启动白色的计时器,如果再次按下,则会停止白色的计时器,但不会启动黑色的计时器。我知道这是因为我构建了 if() 条件 的方式,还因为我不知道如何从外部停止计时器。我所做的是对每个白色和黑色使用 - bool checkTimerWcheckTimerB,我在 if( ) 取消定时器的条件是基于它的。

代码:

提供者 -

import 'dart:async';

import 'package:flutter/foundation.dart';

class SettingsProvider extends ChangeNotifier {
  int valueW = 0;
  int valueB = 0;

// * with these booleans we will stop the timer.
  bool checkTimerW = true;
  bool checkTimerB = true;


  String timeToDisplayW = ""; // for white
  String timeToDisplayB = ""; // for black

  bool switchT = false;

   // this is called in the settings Modal Bottom Sheet
  void changeValue(int valW, int valB) {
    
   //? Changing the value in seconds
    valueW = valW * 60;
    valueB = valB * 60;

    print(valueW);

    timeToDisplayW = valueW.toString();
    timeToDisplayB = valueB.toString();
    notifyListeners();
  }

  void reset() {
    started = true;
    stopped = true;
    checkTimerW = false;
    checkTimerB = false;
    notifyListeners();
  }

  void toggleSwitch() {
    if (switchT == false) {
      switchT = true;
      print('true');
    } else if (switchT == true) {
      switchT = false;
      print('false');
    }
  }

  void switchTimer() {
    if (switchT == false) {

      // Starts white's timer

      Timer.periodic(
        Duration(seconds: 1),
        (Timer t) {
          if (valueW <= 1 || checkTimerW == false) {
            t.cancel();
            checkTimerW = true;

            // TODO : Black Won

            notifyListeners();
          } else {
            valueW = valueW - 1;
            notifyListeners();
          }

          timeToDisplayW = valueW.toString();
          notifyListeners();
        },
      );

      // stops black's timer

      checkTimerB = false;
      toggleSwitch();
      notifyListeners();

    } else {

      // Starts black's timer

      Timer.periodic(
        Duration(seconds: 1),
        (Timer t) {
          if (valueB <= 1 || checkTimerB == false) {
            t.cancel();
            checkTimerB = true;

            // TODO : White won

            notifyListeners();
          } else {
            valueB = valueB - 1;
            notifyListeners();
          }

          timeToDisplayB = valueB.toString();
          notifyListeners();
        },
      );

      // stops white's timer
      checkTimerW = false;
      toggleSwitch();
      notifyListeners();
      
  }
 }
}

Main.dart -

import 'dart:math';


import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';

import 'controller/countdown_controller.dart';
import 'widgets/blackButton.dart';
import 'widgets/bottom_sheet_design.dart';
import 'widgets/whiteButton.dart';
import 'providers/settings.dart';


void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    SystemChrome.setPreferredOrientations([
      DeviceOrientation.portraitUp,
      DeviceOrientation.portraitDown,
    ]);
    return ChangeNotifierProvider(
      create: (ctx) => SettingsProvider(),
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.amber,
          visualDensity: VisualDensity.adaptivePlatformDensity,
        ),
        home: MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
      
  void settings(BuildContext ctx) {
    showModalBottomSheet(
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.only(
          topLeft: Radius.circular(10),
          topRight: Radius.circular(10),
        ),
      ),
      context: ctx,
      builder: (_) => BottomSheetDesign(),
    );
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        backgroundColor: Colors.grey[350],
        body: Row(
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            Expanded(
              flex: 1,
              child: Transform.rotate(
                angle: pi / 1,
                child: GestureDetector(
                  onTap: () {
                    Provider.of<SettingsProvider>(context, listen: false)
                        .switchTimer();
                  },
                  child: Container(
                    width: 80.0,
                    height: 500,
                    child: Center(
                      child: Transform.rotate(
                        angle: pi / 2,
                        child: Text('Switch',
                            style: Theme.of(context).textTheme.bodyText2),
                      ),
                    ),
                    decoration: BoxDecoration(
                      color: Colors.blueGrey,
                    ),
                  ),
                ),
              ),
            ),
            VerticalDivider(),
            Expanded(
              flex: 4,
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Row(
                    children: <Widget>[

                      // container that displays black's timer
                      BlackButton(),

                      Expanded(
                        child: Transform.rotate(
                          angle: pi / 2,
                          child: RaisedButton(
                            onPressed: () {
                              settings(context);
                            },
                            color: Colors.blue[300],
                            child: Text('Settings'),
                          ),
                        ),
                      ),
                    ],
                  ),
                  SizedBox(
                    height: 20,
                  ),
                  Row(
                    children: <Widget>[

                      // container that displays white's timer
                      WhiteButton(),

                      Expanded(
                        child: Transform.rotate(
                          angle: pi / 2,
                          child: RaisedButton(
                            onPressed: () {
                              Provider.of<SettingsProvider>(context, listen: false).reset();
                            },
                            color: Colors.red[600],
                            child: Text('Reset'),
                          ),
                        ),
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

【问题讨论】:

    标签: flutter dart timer countdowntimer flutter-timer


    【解决方案1】:

    我过去已经对此进行了编码。它可能会对你有所帮助。

    class DoubleTimer extends StatefulWidget {
      @override
      _DoubleTimerState createState() => _DoubleTimerState();
    }
    
    class _DoubleTimerState extends State<DoubleTimer> {
      int timeToGoA = 50000;
      int timeToGoB = 50000;
    
      int state = 0; //0: waiting, 1: counting A, 2: counting B
    
      DateTime timeStamp;
    
      _DoubleTimerState() {
        print("init");
      }
    
      @override
      Widget build(BuildContext context) {
        print(
            "${DateTime.now().compareTo(DateTime.now().add(Duration(seconds: 1)))}");
        return Row(
          children: <Widget>[
            if (state == 1)
              ToTime(timeStamp.add(Duration(milliseconds: timeToGoA))),
            FlatButton(
              onPressed: () {
                setState(() {
                  switch (state) {
                    case 0:
                      state = 1;
                      timeStamp = DateTime.now();
                      print("Running A");
                      break;
                    case 1:
                      state = -1;
                      timeToGoA -=
                          DateTime.now().difference(timeStamp).inMilliseconds;
                      timeStamp = DateTime.now();
                      print("A: $timeToGoA\nRunning B");
                      break;
                    case -1:
                      state = 1;
                      timeToGoB -=
                          DateTime.now().difference(timeStamp).inMilliseconds;
                      timeStamp = DateTime.now();
                      print("B: $timeToGoB\nRunning A");
                      break;
                  }
                });
              },
              child: Text("switch"),
            ),
            if (state == -1)
              ToTime(timeStamp.add(Duration(milliseconds: timeToGoB))),
          ],
        );
      }
    }
    
    class ToTime extends StatelessWidget {
      final DateTime timeStamp;
    
      const ToTime(this.timeStamp, {Key key}) : super(key: key);
    
      static final Map<String, int> _times = <String, int>{
        'y': -const Duration(days: 365).inMilliseconds,
        'm': -const Duration(days: 30).inMilliseconds,
        'w': -const Duration(days: 7).inMilliseconds,
        'd': -const Duration(days: 1).inMilliseconds,
        'h': -const Duration(hours: 1).inMilliseconds,
        '\'': -const Duration(minutes: 1).inMilliseconds,
        '"': -const Duration(seconds: 1).inMilliseconds,
        "ms": -1,
      };
    
      Stream<String> get relativeStream async* {
        while (true) {
          int duration = DateTime.now().difference(timeStamp).inMilliseconds;
          String res = '';
          int level = 0;
          int levelSize;
          for (MapEntry<String, int> time in _times.entries) {
            int timeDelta = (duration / time.value).floor();
            if (timeDelta > 0) {
              levelSize = time.value;
              res += '$timeDelta${time.key} ';
              duration -= time.value * timeDelta;
              level++;
            }
            if (level == 2) {
              break;
            }
          }
          levelSize ??= _times.values.reduce(min);
          if (level > 0 && level < 2) {
            List<int> _tempList =
                _times.values.where((element) => (element < levelSize)).toList();
    
            if (_tempList.isNotEmpty) levelSize = _tempList.reduce(max);
          }
          if (res.isEmpty) {
            yield 'now';
          } else {
            res.substring(0, res.length - 2);
            yield res;
          }
    //      print('levelsize $levelSize sleep ${levelSize - duration}ms');
          await Future.delayed(Duration(milliseconds: levelSize - duration));
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return StreamBuilder<String>(
            stream: relativeStream,
            builder: (context, snapshot) {
              return Text(snapshot.data ?? '??');
            });
      }
    }
    

    【讨论】:

    • 嘿伙计,我把这段代码放在 dartpad 中来摆弄它。它抛出一个错误-> 1) levelSize ??= _times.values.reduce(min); 2) if (_tempList.isNotEmpty) levelSize = _tempList.reduce(max);错误:最小值和最大值未定义
    • 您必须在文件顶部添加import 'dart:math';
    【解决方案2】:

    我在没有提供者的情况下对其进行编码(仅使用 ValueNotifier)只是为了向您展示逻辑

    enum Player{White, Black}
    
    class MyTimer extends ValueNotifier<int>{
      Player _turn; //White starts
      int _minutes;
      int _whiteTime;
      int _blackTime;
      
      MyTimer(int time) : 
           _minutes = time * 60,
           _whiteTime = time * 60,
           _blackTime = time * 60,
           _turn = Player.White, //White starts
           super(time * 60 * 2);
      
      bool get _isWhiteTurn => Player.White == _turn;
      
      String get timeLeft{
        if(value != 0){
          //int time = _isWhiteTurn ? _whiteTime : _blackTime; //use this instead of playerTime if you want to display the time in seconds
          Duration left = Duration(seconds: _isWhiteTurn ? _whiteTime : _blackTime);
          String playerTime = left.toString();
          playerTime = playerTime.substring(0, playerTime.lastIndexOf('.'));
          return '${describeEnum(_turn)} turn time left : $playerTime';
        } 
        else{
          return '${describeEnum(_turn)} wins!'; //We have a winner
        }
      }
      
      void switchPlayer() => _turn = _isWhiteTurn ? Player.Black : Player.White;
      void reset([int time]){
        if(time != null) _minutes = time * 60; //if you want to start with a different  value
        _turn = Player.White; //White starts
        _whiteTime = _minutes; //reset time
        _blackTime = _minutes; //reset time
        value = 2*_minutes; //reset time
        //twice as long because it counts the whole time of the match (the time of the 2 players)
      }
      void start(){
        _initilizeTimer();
      }
      void _initilizeTimer(){
        Timer.periodic(
          Duration(seconds: 1),
          (Timer t) {
            if(_whiteTime == 0 || _blackTime == 0){
              t.cancel();
              switchPlayer(); //the time of one player ends, so it switch to the winner player
              value = 0; //end the game
            }
            else{
              _isWhiteTurn ? --_whiteTime : --_blackTime;
              --value;
            }
          },
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      final MyTimer clock = MyTimer(1);
      
      @override
      void initState(){
        super.initState();
        clock.start();
      }
      
      @override
      void dispose(){
        clock.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return SafeArea(
          child: Scaffold(
            backgroundColor: Colors.grey[350],
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  ValueListenableBuilder<int>(
                    valueListenable: clock,
                     builder: (context, unit, _) =>
                       Text(clock.timeLeft ,style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500))
                  ),
                  RaisedButton(
                    child: Text('Switch'),
                    onPressed: () => clock.switchPlayer(),
                  )
                ],
              ),
            )
          ),
        );
      }
    }
    

    这个想法是一样的,但我想告诉你的是,你可以只使用一个计时器来完成整个逻辑,并在两分钟之间改变一些枚举值(白色和黑色)。

    按钮更改玩家的回合(switchPlayer 方法),在计时器内,您会看到根据玩家的回合,它会减少时间_isWhiteTurn ? --_whiteTime : --_blackTime;。作为 ValueNotifier,它仅在值更改时更新,但您可以将 Provider 与 ChangeNotifier 一起使用并在需要时更新(这更好,因为在我的示例中更改播放器时,我仍然必须等待第二个结束,以便计时器更新文本)。

    您可以尝试使用枚举更改类似的内容以简化计时器逻辑

    bool get _isWhiteTurn => Player.White == _turn;
    
    void startMatch() {
          Timer.periodic(
            Duration(seconds: 1),
            (Timer t) {
              if (valueW == 0 || valueB == 0) {
                t.cancel();
                if(valueW == 0) checkTimerB = true;
                else checkTimerW = true
                //it won the one whose time didn't end
              } else {
                _isWhiteTurn ? --valueW : --valueB;
              }
    
              timeToDisplayW = valueW.toString();
              timeToDisplayB = valueB.toString();
              //only one of them will change
              notifyListeners();
            },
          );
    }
    
    void switchTimer(){
      _turn = _isWhiteTurn ? Player.Black : Player.White;
      notifyListeners();
    }
    

    这样你只有一个计时器,整个比赛将在其中一个计时器变为 0 时取消(或者如果有人失控,但我猜这是其他 Provider 中的其他逻辑)

    更新

    您可以将 timeLeft getter 更改为类似的内容

    String get timeLeft{
        if(value != 0){
          //int time = _isWhiteTurn ? _whiteTime : _blackTime; //use this instead of playerTime if you want to display the time in seconds
          Duration white = Duration(seconds: _whiteTime);
          Duration black = Duration(seconds: _blackTime);
          String whiteTime = white.toString();
          String blackTime = black.toString();
          whiteTime = whiteTime.substring(0, whiteTime.lastIndexOf('.'));
          blackTime = blackTime.substring(0, blackTime.lastIndexOf('.'));
          return '''
                    ${describeEnum(Player.White)} time left : $whiteTime
                    ${describeEnum(Player.Black)} time left : $blackTime
          ''';
        } 
        else{
          return '${describeEnum(_turn)} wins!'; //We have a winner
        }
    }
    

    这样它会返回一个包含两个时间的字符串,并且只有玩家的计时器会每秒改变。但正如我所说,用 ChangeNotifierProvider 尝试这个逻辑,它也应该可以工作,你可以在你的小部件树的不同部分使用它

    【讨论】:

    • 嘿伙计,事情是,变量'checkTimerW'和'checkTimerB'只有在-Timer.periodic写入的if条件中指定时才能取消计时器。否则那些变量没有意义。这就是我的意思-> Timer.periodic( Duration(seconds: 1), (Timer t) { if (valueB
    • 也想添加这个。检查计时器的主要目的是暂停没有轮到的一方的计时器。
    • 我了解您在满足条件时取消计时器,但是只有一个计时器您可以检查条件以在其中一个 int 值达到 0(有人超时)时取消,然后使用 checkTimerW并 checkTimerB 知道谁的时间结束了
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-06-17
    • 1970-01-01
    • 1970-01-01
    • 2011-02-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多