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

如何在Flutter中实现秒表?求UI设计与动画添加指导

Flutter 秒表实现指南(含UI设计与动画)

一、UI布局调整

参考你提供的预期UI,在现有基础上补全控制区域、优化排版:

  • 顶部日期文本保留现有样式,添加上下间距让布局更舒展
  • 中间计时文本放大至48号字,强化视觉核心地位
  • 底部用Row排列开始/暂停/重置按钮,设置圆角和间距,提升交互质感

二、核心计时逻辑实现

秒表需要实时更新状态,必须使用StatefulWidget配合Timer实现计时:

  1. 定义状态变量:
int _totalSeconds = 0;
Timer? _timer;
bool _isRunning = false;
  1. 编写计时控制方法:
// 切换开始/暂停状态
void _toggleTimer() {
  if (_isRunning) {
    _timer?.cancel();
  } else {
    _timer = Timer.periodic(const Duration(seconds: 1), (timer) {
      setState(() {
        _totalSeconds++;
      });
    });
  }
  setState(() {
    _isRunning = !_isRunning;
  });
}

// 重置计时
void _resetTimer() {
  _timer?.cancel();
  setState(() {
    _totalSeconds = 0;
    _isRunning = false;
  });
}

// 格式化时间为 HH:MM:SS 格式
String _formatTime(int seconds) {
  int hours = seconds ~/ 3600;
  int minutes = (seconds % 3600) ~/ 60;
  int secs = seconds % 60;
  return "${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${secs.toString().padLeft(2, '0')}";
}

三、添加动画效果

1. 计时数字切换动画

用AnimatedSwitcher实现数字变化时的淡入淡出效果,替换原有静态Text:

AnimatedSwitcher(
  duration: const Duration(milliseconds: 300),
  transitionBuilder: (Widget child, Animation<double> animation) {
    return FadeTransition(opacity: animation, child: child);
  },
  child: Text(
    _formatTime(_totalSeconds),
    key: ValueKey<int>(_totalSeconds),
    style: const TextStyle(fontSize: 48, fontWeight: FontWeight.bold),
  ),
)

2. 按钮交互动画

使用InkWell包裹按钮,自带水波纹点击动画,同时设置按钮样式:

InkWell(
  onTap: _toggleTimer,
  borderRadius: BorderRadius.circular(8),
  child: Container(
    padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
    color: _isRunning ? Colors.red : Colors.green,
    child: Text(
      _isRunning ? '暂停' : '开始',
      style: const TextStyle(color: Colors.white, fontSize: 16),
    ),
  ),
)

完整代码示例

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

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

  @override
  State<StopwatchPage> createState() => _StopwatchPageState();
}

class _StopwatchPageState extends State<StopwatchPage> {
  int _totalSeconds = 0;
  Timer? _timer;
  bool _isRunning = false;

  void _toggleTimer() {
    if (_isRunning) {
      _timer?.cancel();
    } else {
      _timer = Timer.periodic(const Duration(seconds: 1), (timer) {
        setState(() {
          _totalSeconds++;
        });
      });
    }
    setState(() {
      _isRunning = !_isRunning;
    });
  }

  void _resetTimer() {
    _timer?.cancel();
    setState(() {
      _totalSeconds = 0;
      _isRunning = false;
    });
  }

  String _formatTime(int seconds) {
    int hours = seconds ~/ 3600;
    int minutes = (seconds % 3600) ~/ 60;
    int secs = seconds % 60;
    return "${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${secs.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(
              DateTime.now().toLocal().toString().split('.')[0],
              style: const TextStyle(fontSize: 18),
            ),
            const SizedBox(height: 32),
            // 带动画的计时数字
            AnimatedSwitcher(
              duration: const Duration(milliseconds: 300),
              transitionBuilder: (Widget child, Animation<double> animation) {
                return FadeTransition(opacity: animation, child: child);
              },
              child: Text(
                _formatTime(_totalSeconds),
                key: ValueKey<int>(_totalSeconds),
                style: const TextStyle(fontSize: 48, fontWeight: FontWeight.bold),
              ),
            ),
            const SizedBox(height: 48),
            // 控制按钮组
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              gap: 16,
              children: [
                InkWell(
                  onTap: _toggleTimer,
                  borderRadius: BorderRadius.circular(8),
                  child: Container(
                    padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
                    color: _isRunning ? Colors.red : Colors.green,
                    child: Text(
                      _isRunning ? '暂停' : '开始',
                      style: const TextStyle(color: Colors.white, fontSize: 16),
                    ),
                  ),
                ),
                InkWell(
                  onTap: _resetTimer,
                  borderRadius: BorderRadius.circular(8),
                  child: Container(
                    padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
                    color: Colors.grey,
                    child: const Text(
                      '重置',
                      style: TextStyle(color: Colors.white, fontSize: 16),
                    ),
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

// 运行示例
void main() {
  runApp(const MaterialApp(home: StopwatchPage()));
}

内容的提问来源于stack exchange,提问作者Farhan Sadiq

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 17:01:17