Dart - 在 x 秒后执行函数,除非被事件取消

Dart - execute a function after x seconds unless cancelled by event

我目前正在使用 FlutterDart 编写应用程序。在按钮 onPressed 事件上,我想调用一个在 timeLeft 秒后执行的操作,除非通过正确输入 pin 取消它。此外,我想在文本小部件中使用值 timeLeft

这需要一个具有以下功能的结构:

我想知道如何根据 flutter 和 dart 的最佳实践来做到这一点。对于状态管理,我使用提供者模式,因此这种方法最好与提供者模式兼容。

这是我目前尝试过的方法:

class Home extends ChangeNotifier {
  int secondsLeft = 10;

  void onPressedEmergencyButton(BuildContext context) {
    countDown();
    showDialog<void>(
      context: context,
      builder: (context) {
        return ScreenLock(
          title: Text(
              "Sending message in ${context.read<Home>().secondsLeft} seconds"),
          correctString: '1234',
          canCancel: false,
          didUnlocked: () {
            Navigator.pop(context);
          },
        );
      },
    );
  }

  void countDown() {
    Future.delayed(const Duration(seconds: 1), () {
      secondsLeft =- 1;
      notifyListeners();
      if (secondsLeft <= 0) {
        // Do something
        return;
      }
    });
  }
}

您可以使用 async 包中的 CancelableOperation

简化代码片段和关于 _cancelTimer(bool) ,这个 bool 用于告诉小部件关于 true = time end 和取消 false 就像 _cancelTimer(false);,其余的在代码中描述-评论。

class TS extends StatefulWidget {
  const TS({Key? key}) : super(key: key);

  @override
  State<TS> createState() => _TSState();
}

class _TSState extends State<TS> {
  Timer? _timer;
  final Duration _refreseRate = const Duration(seconds: 1);

  CancelableOperation? _cancelableOperation;

  Duration taskDuration = const Duration(seconds: 5);

  bool isSuccess = false;

  _initTimer() {
    if (_cancelableOperation != null) {
      _cancelTimer(false);
    }

    _cancelableOperation = CancelableOperation.fromFuture(
      Future.delayed(Duration.zero),
    ).then((p0) {
      _timer = Timer.periodic(_refreseRate, (timer) {
        setState(() {
          taskDuration -= _refreseRate;
        });
        if (taskDuration <= Duration.zero) {
          /// task complete on end of duration

          _cancelTimer(true);
        }
      });
    }, onCancel: () {
      _timer?.cancel();

      setState(() {});
    });
  }

  _cancelTimer(bool eofT) {
    // cancel and reset everything
    _cancelableOperation?.cancel();
    _timer?.cancel();
    _timer = null;
    taskDuration = const Duration(seconds: 5);
    isSuccess = eofT;
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            if (isSuccess)
              Container(
                height: 100,
                width: 100,
                color: Colors.green,
              ),
            if (_timer != null)
              Text("${taskDuration.inSeconds}")
            else
              const Text("init Timer"),
          ],
        ),
      ),
      floatingActionButton: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          FloatingActionButton(
            child: const Text("init"),
            onPressed: () {
              _initTimer();
            },
          ),
          FloatingActionButton(
            child: const Text("Cancel"),
            onPressed: () {
              _cancelTimer(false);
            },
          ),
        ],
      ),
    );
  }
}

你可以使用Timerclass到运行一个函数后设置Duration。它不会给你剩余时间,但你可以自己计算。

这是我整理的一个快速实现:

import 'dart:async';

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.light(),
      home: const Scaffold(
        body: Center(
          child: Countdown(),
        ),
      ),
    );
  }
}

class Countdown extends StatefulWidget {
  const Countdown({Key? key}) : super(key: key);

  @override
  _CountdownState createState() => _CountdownState();
}

class _CountdownState extends State<Countdown> {
  bool active = false;
  Timer? timer;
  Timer? refresh;
  Stopwatch stopwatch = Stopwatch();
  Duration duration = const Duration(seconds: 5);

  _CountdownState() {
    // this is just so the time remaining text is updated
    refresh = Timer.periodic(
        const Duration(milliseconds: 100), (_) => setState(() {}));
  }

  void start() {
    setState(() {
      active = true;
      timer = Timer(duration, () {
        stop();
        onCountdownComplete();
      });
      stopwatch
        ..reset()
        ..start();
    });
  }

  void stop() {
    setState(() {
      active = false;
      timer?.cancel();
      stopwatch.stop();
    });
  }

  void onCountdownComplete() {
    showDialog(
      context: context,
      builder: (context) => const AlertDialog(
        title: Text('Countdown was not stopped!'),
      ),
    );
  }

  int secondsRemaining() {
    return duration.inSeconds - stopwatch.elapsed.inSeconds;
  }

  @override
  void dispose() {
    timer?.cancel();
    refresh?.cancel();
    stopwatch.stop();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        if (active) Text(secondsRemaining().toString()),
        if (active)
          TextButton(onPressed: stop, child: const Text('Stop'))
        else
          TextButton(onPressed: start, child: const Text('Start')),
      ],
    );
  }
}