Flutter应用开发:实现从当前日期开始的正向实时天数计数器
实现正向实时天数计数器的方案
核心思路
不用依赖现有倒计时包,自己实现正向计时逻辑更灵活:
- 记录启动时刻(用户点击启动时的
DateTime.now()) - 用
Timer.periodic每秒计算当前时间与启动时刻的时间差 - 将差值转换为天、时、分、秒格式展示
- 停止时计算总天数并通过本地存储持久化
具体实现步骤
1. 核心计时逻辑
用StatefulWidget维护计数器状态,关键变量包括启动时间、定时器、累计流逝时长和运行状态:
DateTime? _startTime; Timer? _timer; Duration _elapsed = Duration.zero; bool _isRunning = false;
启动计数器时,记录当前时间并启动定时器:
void _startTimer() { if (!_isRunning) { _startTime = DateTime.now(); _timer = Timer.periodic(const Duration(seconds: 1), (timer) { setState(() { _elapsed = DateTime.now().difference(_startTime!); }); }); setState(() => _isRunning = true); } }
停止计数器时,取消定时器并计算总天数:
void _stopTimer() { if (_isRunning) { _timer?.cancel(); setState(() => _isRunning = false); // 计算总天数(可根据需求选择取整或保留小数) final totalDays = _elapsed.inSeconds / (24 * 60 * 60); _saveTotalDays(totalDays); } }
2. 时间格式转换
把Duration对象转换成易读的天、时、分、秒字符串:
String _formatDuration(Duration duration) { final days = duration.inDays; final hours = duration.inHours.remainder(24); final minutes = duration.inMinutes.remainder(60); final seconds = duration.inSeconds.remainder(60); return '$days天 ${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}'; }
3. 本地存储总天数
使用shared_preferences实现本地持久化,先在pubspec.yaml添加依赖:
dependencies: shared_preferences: ^2.2.2
实现存储和读取方法:
import 'package:shared_preferences/shared_preferences.dart'; Future<void> _saveTotalDays(double days) async { final prefs = await SharedPreferences.getInstance(); // 累加已存储的天数,也可根据需求直接覆盖 final savedDays = prefs.getDouble('total_days') ?? 0.0; await prefs.setDouble('total_days', savedDays + days); } // 初始化时读取已存储的总天数 Future<void> _loadSavedDays() async { final prefs = await SharedPreferences.getInstance(); setState(() { _savedTotalDays = prefs.getDouble('total_days') ?? 0.0; }); }
4. 完整UI示例
整合所有逻辑到Widget中:
import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; class DaysCounter extends StatefulWidget { const DaysCounter({super.key}); @override State<DaysCounter> createState() => _DaysCounterState(); } class _DaysCounterState extends State<DaysCounter> { DateTime? _startTime; Timer? _timer; Duration _elapsed = Duration.zero; bool _isRunning = false; double _savedTotalDays = 0.0; @override void initState() { super.initState(); _loadSavedDays(); } Future<void> _loadSavedDays() async { final prefs = await SharedPreferences.getInstance(); setState(() { _savedTotalDays = prefs.getDouble('total_days') ?? 0.0; }); } void _startTimer() { if (!_isRunning) { _startTime = DateTime.now(); _timer = Timer.periodic(const Duration(seconds: 1), (timer) { setState(() { _elapsed = DateTime.now().difference(_startTime!); }); }); setState(() => _isRunning = true); } } void _stopTimer() { if (_isRunning) { _timer?.cancel(); setState(() => _isRunning = false); final totalDays = _elapsed.inSeconds / (24 * 60 * 60); _saveTotalDays(totalDays); } } Future<void> _saveTotalDays(double days) async { final prefs = await SharedPreferences.getInstance(); final updatedDays = _savedTotalDays + days; await prefs.setDouble('total_days', updatedDays); setState(() { _savedTotalDays = updatedDays; }); } String _formatDuration(Duration duration) { final days = duration.inDays; final hours = duration.inHours.remainder(24); final minutes = duration.inMinutes.remainder(60); final seconds = duration.inSeconds.remainder(60); return '$days天 ${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}'; } @override void dispose() { _timer?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('天数计数器')), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( _formatDuration(_elapsed), style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold), ), const SizedBox(height: 20), Text( '累计总天数: ${_savedTotalDays.toStringAsFixed(2)}', style: const TextStyle(fontSize: 18), ), const SizedBox(height: 40), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: _isRunning ? null : _startTimer, child: const Text('启动'), ), const SizedBox(width: 20), ElevatedButton( onPressed: _isRunning ? _stopTimer : null, child: const Text('停止'), ), ], ), ], ), ), ); } }
关键注意点
- 如果需要暂停后继续计时,不要重置
_startTime,而是记录暂停时的_elapsed,下次启动时用DateTime.now().subtract(_elapsed)作为新的启动时间 - 存储时可根据需求选择取整(
_elapsed.inDays)或保留小数(精确到秒转换的天数) - 每秒触发一次定时器足够满足实时展示需求,避免更短间隔影响性能
内容的提问来源于stack exchange,提问作者Solomon Girma
相关产品推荐
相关产品推荐

