web-dev-qa-db-fra.com

Flutter - Créer un widget compte à rebours

J'essaie de construire un widget compte à rebours. Actuellement, j'ai la structure pour travailler. Je ne lutte qu'avec le compte à rebours lui-même. J'ai essayé cette approche en utilisant le plugin countdown:

class _Countdown extends State<Countdown> {

  int val = 3;

  void countdown(){
    CountDown cd = new CountDown(new Duration(seconds: 4));

    cd.stream.listen((Duration d) {
      setState((){
        val = d.inSeconds;
      });
    });

  }

  @override
  build(BuildContext context){
    countdown();
    return new Scaffold(
      body: new Container(
        child: new Center(
          child: new Text(val.toString(), style: new TextStyle(fontSize: 150.0)),
        ),
      ),
    );
  }
}

Cependant, la valeur change très étrangement et pas du tout. Ça commence à trembler. Toute autre approche ou solution?

7
OhMad

On dirait que vous essayez d’afficher un widget texte animé qui change avec le temps. J'utiliserais un AnimatedWidget avec un StepTween pour m'assurer que le compte à rebours ne montre que des valeurs entières.

 countdown

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

basé sur la réponse @ raju-bitter, alternative à l'utilisation asynchrone/wait sur le flux du compte à rebours

  void countdown() async {
    cd = new CountDown(new Duration(seconds:4));
    await for (var v in cd.stream) {
      setState(() => val = v.inSeconds);
    }
  }
1
Supawat Pusavanno

La méthode countdown() doit être appelée à partir de la méthode initState() de l'objet State.

class _CountdownState extends State<CountdownWidget> {

  int val = 3;
  CountDown cd;

  @override
  void initState() {
    super.initState();
    countdown();
  }
...

Description de initState() à partir des documents Flutter :

Le cadre appelle initState. Les sous-classes d’État doivent prévaloir sur initState pour effectuer une initialisation unique qui dépend du BuildContext ou le widget, disponibles en tant que contexte et propriétés du widget, respectivement, lorsque la méthode initState est appelée.

Voici un exemple de travail complet:

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();
}
1
raju-bitter

Exemple de compte à rebours utilisant stream, n'utilisant pas setState(...), son tout est donc sans état.

cette idée d'emprunt de l'exemple 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;
  }
}

La différence avec stateful est que recharger l'application redémarre le comptage. Lorsque vous utilisez stateful, dans certains cas, il peut ne pas redémarrer lors du rechargement.

0
Supawat Pusavanno