- 演示
点击后开始倒计时,推出当前界面,计时依然会继续,不会导致内存泄漏,实现原理是保存第一次开始计时的当前时间到本地,当再次进入时,取出退出时保存的值,然后跟当前值对比,得出差值,然后再跟计时时间相减,得出已倒计时耗费时间,然后减去该耗费时间,根据剩余时间继续计时。
- 功能
1.可设置倒计时值。
2.点击会回调state,可手动开始计时器。
3.可保存绝对时间,不间断计时。
4.可设置计时完成后显示值。
- 代码
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:xzg_pda_flutter/components/button/kq_small_button.dart';
import '../../config/global.dart';
import '../../resources/l10n/l10n.dart';
class CountDownButton extends StatefulWidget {
/// 倒计时数值
final int countDownValue;
/// session值,保持界面绝对倒计时
final String session;
/// 非计时时显示的按钮文字
final String? text;
/// 点击后的回调
final Function(CountDownButtonState state)? onTap;
const CountDownButton({
super.key,
required this.countDownValue,
required this.session,
this.text,
this.onTap,
});
@override
State<StatefulWidget> createState() => CountDownButtonState();
}
class CountDownButtonState extends State<CountDownButton> {
final String boolSuffix = "BOOL";
final String intSuffix = "INT";
/// 当前进度文本
late String _text;
/// 按钮是否不可用
bool _disabled = false;
/// 计时器
Timer? timer;
/// 开始计时时的时间戳
int? _startTimeMillis;
/// 是否是计时状态
bool? _isStart;
@override
void initState() {
super.initState();
_text = widget.text ?? S.of(context).confirm;
_isStart = Global.prefs?.getBool(widget.session + boolSuffix) ?? false;
int currentTimeMillis = DateTime.now().millisecondsSinceEpoch;
int startTimeMillis = Global.prefs?.getInt(widget.session + intSuffix) ?? 0;
if (startTimeMillis != 0 &&
(currentTimeMillis - startTimeMillis) / 1000 <= widget.countDownValue &&
(_isStart ?? false)) {
startTimer();
}
}
void startTimer() {
int count = 0;
int currentTimeMillis = DateTime.now().millisecondsSinceEpoch;
int startTimeMillis = Global.prefs?.getInt(widget.session + intSuffix) ?? 0;
if (startTimeMillis == 0 ||
(currentTimeMillis - startTimeMillis) / 1000 > widget.countDownValue) {
_startTimeMillis = currentTimeMillis;
} else {
count = (currentTimeMillis - startTimeMillis) ~/ 1000;
_startTimeMillis = startTimeMillis;
}
//计时器,每1秒执行一次
const period = Duration(seconds: 1);
timer = Timer.periodic(period, (timer) {
count++;
num max = widget.countDownValue;
//计时器结束条件
if (widget.countDownValue == 0 || count >= max) {
timer.cancel();
if (mounted) {
_isStart = false;
Global.prefs?.setBool(widget.session + boolSuffix, false);
Global.prefs?.setInt(widget.session + intSuffix, 0);
setState(() {
_text = widget.text ?? S.of(context).confirm;
_disabled = false;
});
}
} else {
if (mounted) {
_isStart = true;
setState(() {
_text = (widget.countDownValue - count).toString();
_disabled = true;
});
}
}
});
}
@override
void dispose() {
//退出时关闭计时器防止内存泄露
timer?.cancel();
Global.prefs?.setBool(widget.session + boolSuffix, _isStart ?? false);
Global.prefs?.setInt(widget.session + intSuffix, _startTimeMillis ?? 0);
super.dispose();
}
@override
Widget build(BuildContext context) {
return KqSmallButton(
disabled: _disabled,
title: _text,
onTap: (disabled) {
if (widget.onTap != null && !disabled) {
widget.onTap!(this);
}
},
);
}
}
- 使用
CountDownButton(
countDownValue: 30,
session: "login_view",
text: "点我测试",
onTap: (state) {
state.startTimer();
},
)