You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

咨询:Flutter中用Shared Preferences实现计时器续计时方案

如何用SharedPreferences实现Flutter计时器重启后续计时

核心思路

要实现关闭应用再打开后计时器继续计时,需要存储两个关键数据:

  • totalElapsed:累计已经度过的毫秒数
  • lastStartTime:上次开始计时的时间戳(如果计时器未处于运行状态,该值为null)

当应用重启时:

  1. 读取存储的totalElapsed和lastStartTime
  2. 如果lastStartTime不为空,说明上次退出时计时器正在运行,需要计算从上次开始时间到当前时间的差值,加到totalElapsed上
  3. 基于更新后的totalElapsed继续计时

步骤1:添加依赖

在pubspec.yaml中引入shared_preferences包:

dependencies:
  flutter:
    sdk: flutter
  shared_preferences: ^2.2.2 # 替换为最新版本即可

步骤2:完整实现代码

以下是可直接运行的示例页面:

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

class ResumableTimerPage extends StatefulWidget {
  const ResumableTimerPage({super.key});

  @override
  State<ResumableTimerPage> createState() => _ResumableTimerPageState();
}

class _ResumableTimerPageState extends State<ResumableTimerPage> {
  Timer? _timer;
  int _totalElapsed = 0; // 累计已过毫秒数
  int? _lastStartTime; // 上次开始计时的时间戳(毫秒)
  bool _isRunning = false;

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

  @override
  void dispose() {
    _timer?.cancel();
    _saveTimerData();
    super.dispose();
  }

  // 读取存储的计时器数据
  Future<void> _loadSavedTimerData() async {
    final prefs = await SharedPreferences.getInstance();
    setState(() {
      _totalElapsed = prefs.getInt('totalElapsed') ?? 0;
      _lastStartTime = prefs.getInt('lastStartTime');
    });

    // 上次退出时计时器在运行,直接恢复计时
    if (_lastStartTime != null) {
      _resumeTimer();
    }
  }

  // 保存计时器数据到SharedPreferences
  Future<void> _saveTimerData() async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setInt('totalElapsed', _totalElapsed);
    await prefs.setInt('lastStartTime', _lastStartTime ?? 0);
  }

  // 开始/恢复计时
  void _startTimer() {
    if (_isRunning) return;

    setState(() {
      _lastStartTime = DateTime.now().millisecondsSinceEpoch;
      _isRunning = true;
    });

    _timer = Timer.periodic(const Duration(milliseconds: 100), (timer) {
      final currentTime = DateTime.now().millisecondsSinceEpoch;
      final elapsedSinceLastStart = currentTime - (_lastStartTime ?? currentTime);
      setState(() {
        _totalElapsed += elapsedSinceLastStart;
        _lastStartTime = currentTime;
      });
    });
  }

  // 暂停计时
  void _pauseTimer() {
    if (!_isRunning) return;

    _timer?.cancel();
    final currentTime = DateTime.now().millisecondsSinceEpoch;
    setState(() {
      _totalElapsed += currentTime - (_lastStartTime ?? currentTime);
      _lastStartTime = null;
      _isRunning = false;
    });
    _saveTimerData();
  }

  // 重置计时器
  void _resetTimer() {
    _timer?.cancel();
    setState(() {
      _totalElapsed = 0;
      _lastStartTime = null;
      _isRunning = false;
    });
    _saveTimerData();
  }

  // 恢复上次未完成的计时
  void _resumeTimer() {
    final currentTime = DateTime.now().millisecondsSinceEpoch;
    setState(() {
      _totalElapsed += currentTime - (_lastStartTime ?? currentTime);
      _lastStartTime = currentTime;
      _isRunning = true;
    });

    _timer = Timer.periodic(const Duration(milliseconds: 100), (timer) {
      final now = DateTime.now().millisecondsSinceEpoch;
      setState(() {
        _totalElapsed += now - (_lastStartTime ?? now);
        _lastStartTime = now;
      });
    });
  }

  // 格式化毫秒为时分秒
  String _formatTime(int milliseconds) {
    int seconds = (milliseconds ~/ 1000) % 60;
    int minutes = (milliseconds ~/ (1000 * 60)) % 60;
    int hours = (milliseconds ~/ (1000 * 60 * 60)) % 24;
    return '$hours:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('可恢复计时器')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              _formatTime(_totalElapsed),
              style: const TextStyle(fontSize: 40, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 40),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                  onPressed: _isRunning ? _pauseTimer : _startTimer,
                  child: Text(_isRunning ? '暂停' : '开始'),
                ),
                const SizedBox(width: 20),
                ElevatedButton(
                  onPressed: _resetTimer,
                  child: const Text('重置'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

关键细节说明

  • 数据存储时机:在页面销毁(dispose)和暂停计时时保存数据,确保退出应用时数据不会丢失
  • 计时精度:用millisecondsSinceEpoch获取毫秒级时间戳,保证计时准确性
  • 内存泄漏避免:在dispose方法中取消计时器,防止内存泄漏
  • 异常处理:读取数据时用??设置默认值,避免空指针问题

内容的提问来源于stack exchange,提问作者Jahan Zaib PJ

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 14:02:09