Flutter - Create a countdown widget

前端 未结 5 1920
没有蜡笔的小新
没有蜡笔的小新 2021-02-05 20:58

I am trying to build a countdown widget. Currently, I got the structure to work. I only struggle with the countdown itself. I tried this approach using the countdown plugin:

相关标签:
5条回答
  • 2021-02-05 21:42

    The countdown() method should be called from the initState() method of the State object.

    class _CountdownState extends State<CountdownWidget> {
    
      int val = 3;
      CountDown cd;
    
      @override
      void initState() {
        super.initState();
        countdown();
      }
    ...
    

    Description of initState() from the Flutter docs:

    The framework calls initState. Subclasses of State should override initState to perform one-time initialization that depends on the BuildContext or the widget, which are available as the context and widget properties, respectively, when the initState method is called.

    Here is a full working example:

    import 'dart:async';
    import 'package:flutter/material.dart';
    import 'package:countdown/countdown.dart';
    
    void main() {
      runApp(new MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return new MaterialApp(
          title: 'Countdown Demo',
          theme: new ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: new MyHomePage(),
        );
      }
    }
    
    
    class MyHomePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return new CountdownWidget();
      }
    }
    
    class _CountdownState extends State<CountdownWidget> {
    
      int val = 3;
      CountDown cd;
    
      @override
      void initState() {
        super.initState();
        countdown();
      }
    
      void countdown(){
        print("countdown() called");
        cd = new CountDown(new Duration(seconds: 4));
        StreamSubscription sub = cd.stream.listen(null);
        sub.onDone(() {
          print("Done");
        });
        sub.onData((Duration d) {
          if (val == d.inSeconds) return;
          print("onData: d.inSeconds=${d.inSeconds}");
          setState((){
            val = d.inSeconds;
          });
        });
      }
    
      @override
      build(BuildContext context){
        return new Scaffold(
          body: new Container(
            child: new Center(
              child: new Text(val.toString(), style: new TextStyle(fontSize: 150.0)),
            ),
          ),
        );
      }
    }
    
    class CountdownWidget extends StatefulWidget {
    
      @override
      _CountdownState createState() => new _CountdownState();
    }
    
    0 讨论(0)
  • 2021-02-05 21:45

    It sounds like you are trying to show an animated text widget that changes over time. I would use an AnimatedWidget with a StepTween to ensure that the countdown only shows integer values.

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(new MaterialApp(
        home: new MyApp(),
      ));
    }
    
    class Countdown extends AnimatedWidget {
      Countdown({ Key key, this.animation }) : super(key: key, listenable: animation);
      Animation<int> animation;
    
      @override
      build(BuildContext context){
        return new Text(
          animation.value.toString(),
          style: new TextStyle(fontSize: 150.0),
        );
      }
    }
    
    class MyApp extends StatefulWidget {
      State createState() => new _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
      AnimationController _controller;
    
      static const int kStartValue = 4;
    
      @override
      void initState() {
        super.initState();
        _controller = new AnimationController(
          vsync: this,
          duration: new Duration(seconds: kStartValue),
        );
      }
    
      @override
      Widget build(BuildContext context) {
        return new Scaffold(
          floatingActionButton: new FloatingActionButton(
            child: new Icon(Icons.play_arrow),
            onPressed: () => _controller.forward(from: 0.0),
          ),
          body: new Container(
            child: new Center(
              child: new Countdown(
                animation: new StepTween(
                  begin: kStartValue,
                  end: 0,
                ).animate(_controller),
              ),
            ),
          ),
        );
      }
    }
    
    0 讨论(0)
  • 2021-02-05 21:46

    Countdown example using stream, not using setState(...) therefore its all stateless.

    this borrow idea from example flutter_stream_friends

    import 'dart:async';
    import 'package:flutter/material.dart';
    import 'package:countdown/countdown.dart';
    
    void main() {
      runApp(new MyApp());
    }
    
    class MyApp extends StatelessWidget {
      static String appTitle = "Count down";
    
      @override
      Widget build(BuildContext context) {
        return new MaterialApp(
          title: appTitle,
          theme: new ThemeData(
            primarySwatch: Colors.purple,
          ),
          home: new StreamBuilder(
              stream: new CounterScreenStream(5),
              builder: (context, snapshot) => buildHome(
                  context,
                  snapshot.hasData
                      // If our stream has delivered data, build our Widget properly
                      ? snapshot.data
                      // If not, we pass through a dummy model to kick things off
                      : new Duration(seconds: 5),
                  appTitle)),
        );
      }
    
      // The latest value of the CounterScreenModel from the CounterScreenStream is
      // passed into the this version of the build function!
      Widget buildHome(BuildContext context, Duration duration, String title) {
        return new Scaffold(
          appBar: new AppBar(
            title: new Text(title),
          ),
          body: new Center(
            child: new Text(
              'Count down ${ duration.inSeconds }',
            ),
          ),
        );
      }
    }
    
    class CounterScreenStream extends Stream<Duration> {
      final Stream<Duration> _stream;
    
      CounterScreenStream(int initialValue)
          : this._stream = createStream(initialValue);
    
      @override
      StreamSubscription<Duration> listen(
              void onData(Duration event),
              {Function onError,
              void onDone(),
              bool cancelOnError}) =>
          _stream.listen(onData,
              onError: onError, onDone: onDone, cancelOnError: cancelOnError);
    
      // The method we use to create the stream that will continually deliver data
      // to the `buildHome` method.
      static Stream<Duration> createStream(int initialValue) {
        var cd = new CountDown(new Duration(seconds: initialValue));
        return cd.stream;
      }
    }
    

    The difference from stateful is that reload the app will restart counting. When using stateful, in some cases, it may not restart when reload.

    0 讨论(0)
  • 2021-02-05 21:48

    based on @raju-bitter answer, alternative to use async/await on countdown stream

      void countdown() async {
        cd = new CountDown(new Duration(seconds:4));
        await for (var v in cd.stream) {
          setState(() => val = v.inSeconds);
        }
      }
    
    0 讨论(0)
  • 2021-02-05 21:58

    Why not use a simple TweenAnimationBuilder its easy to use and you don't need to manage any stream controllers or worry about using streams and disposing them off etc;

    TweenAnimationBuilder<double>(
                    duration: Duration(seconds: 10),
                    tween: Tween(begin: 100.0, end: 0.0),
                    onEnd: () {
                      print('Countdown ended');
                    },
                    builder: (BuildContext context, double value, Widget child) {
                      return Padding(
                          padding: const EdgeInsets.symmetric(vertical: 5),
                          child: Text('${value.toInt()}',
                              textAlign: TextAlign.center,
                              style: TextStyle(
                                  color: Colors.black,
                                  fontWeight: FontWeight.bold,
                                  fontSize: 40)));
                    }),
    

    here's the dartpad example to playaround output:

    originally answered here

    0 讨论(0)
提交回复
热议问题