Code Flutter Stop Watch

使用Flutter制作的一款秒表小应用

0x00 页面预览

## 0x01 功能介绍 * 计时 (简直就是废话) * 差值显示 * 极值高亮

0x02 widget分析

  • 一个显示时间的Text组件
  • 具有开始/暂停功能的一个按钮
    • 需要一个状态指示器变量
  • 具有计时/重置功能的一个按钮
    • 需要一个是否重置的指示器变量
  • 显示计时记录的List
    • 需要一个储存每次计时的List变量

Dart内置的DateTime类可以获取当前时间
内置的Timer可以周期性运行指定的函数
二者结合即可写出显示时间的Text组件
widget整体布局如下(括号修的我好苦)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Stack(
Text(), // 显示时间的Text
ListView.builder(
FlatButton( // 为了让List点击之后 具有些视觉效果,所以放置一个会产生涟漪的FlatButton
ListTile(
leading: Text(), // 显示序号
title: Text(), // 显示点下计时按钮的时刻
subtitle:Text() // 显示与上一次计时的差值
),
),
),
FloatingActionButton(), // 开始/暂停按钮,需要需要调用setState
FloatingActionButton(), // 计时/重置按钮,需要需要调用setState
)

0x03 实现代码

TimeTextStateFullWidget.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
import 'package:flutter/material.dart';
import 'dart:async';


class TimeTextStateFullWidget extends StatefulWidget {
@override
_TimeTextStateFullWidgetState createState() => _TimeTextStateFullWidgetState();
}

class _TimeTextStateFullWidgetState extends State<TimeTextStateFullWidget> {
int _timeInit = 0; // 计时开始的时间
int _pauseTime = 0; // 点击暂停的时间用来计算暂停了多久,后期用来减去插值,继续计时
int _pauseAt = 0; // 储存暂停所在时刻
bool isRunning = false; // 秒表状态判断
bool _needInit = true; // 秒表初始化状态判断
Timer timer; // 重复调用需要用到的变量
List<DateTime> laps = List<DateTime>(); // 储存所有计时记录 类型 String
List<int> _deltaList = List<int>(); //储存差值 类型int 单位mill second
int _maxIndex = 0; // 最大值的索引
int _minIndex = 0; // 最小值的索引
int _fixedDelta = 8 * 3600000; // 固定的8小时插值. 描述见下
/// 因为DateTime.fromMillisecondsSinceEpoch()得到的结果是从1970:01:01:08:00开始, 所以需要减去这个8小时
@override
void initState() {
super.initState();
}

@override
Widget build(BuildContext context) {
return Scaffold(
// 为了使右下方悬浮两个FloatActionButton, 所以使用Stack布局
body: Stack(
children: <Widget>[
Column(
children: <Widget>[
Expanded(
flex: 1,
child: Center(
child: Text(
_calculateTime(),
style: TextStyle(
fontSize: 64,
color: Colors.black87,
),
),
),
),
Divider(
height: 1,
),
Expanded(
flex: 4,
child: ListView.builder(
itemBuilder: (context, index) {
Color color = Colors.grey;
if (index == laps.length - 1) {
color = Colors.black;
}
return Card(
child: FlatButton(
onPressed: () {},
padding: EdgeInsets.only(left: 2),
child: ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 4),
dense: true,
leading: Padding(padding: EdgeInsets.only(left: 22),child: Text(
"${index + 1}",
style: TextStyle(fontSize: 18.0),
textAlign: TextAlign.center,
),),
title: Align(
widthFactor: 4,
alignment: Alignment.centerLeft,
child: Padding(padding: EdgeInsets.only(left: 0),child: Text(
_formatDateTime(laps[index]),
style: TextStyle(fontSize: 27.0, color: color),
),),
),
subtitle: Container(
padding: EdgeInsets.only(right: 64),
alignment: Alignment.bottomRight,
child: index > 0
? Text(
"+" +
_formatDateTime(DateTime.fromMillisecondsSinceEpoch(
_deltaList[index - 1] - _fixedDelta)),
style: TextStyle(color: _matchColor(index - 1), fontSize: 14.0),
)
: Text("^_^ "),
),
)),
);
},
itemCount: laps.length,
),
)
],
),
Align(
alignment: Alignment.bottomRight,
child: Container(
padding: EdgeInsets.all(12),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
FloatingActionButton(
onPressed: startRecord,
child: Icon(isRunning ? Icons.pause : Icons.play_arrow),
tooltip: isRunning ? "暂停" : "开始",
backgroundColor: Colors.redAccent,
),
Container(
height: 12,
width: 1,
),
FloatingActionButton(
onPressed: lap,
child: Icon(!isRunning ? Icons.settings_backup_restore : Icons.alarm_add),
tooltip: isRunning ? "计次" : "重置",
backgroundColor: Colors.redAccent,
),
],
),
),
)
],
));
}

// 重置操作
void restartTime() {
laps.clear();
_deltaList.clear();
setState(() {
// 重置时间相关参数
// 重置索引相关参数
_timeInit = 0;
_pauseTime = 0;
_pauseAt = 0;
timer.cancel();
isRunning = false;
_needInit = true;
_maxIndex = 0;
_minIndex = 0;
});
}

// 记录暂停的时间
void updatePauseTime() {
// 更新暂停的时刻
_pauseTime += _calculateMillSecond(DateTime.now()) - _pauseAt;
}

// 更新时间
void _updateTime(Timer timer) {
setState(() {});
}

// 开始计时操作
void startRecord() {
if (isRunning) {
// 暂停操作
isRunning = false;
timer.cancel();

// 储存点击暂停按钮的时刻
_pauseAt = _calculateMillSecond(DateTime.now());
updatePauseTime();
} else {
// 继续或者启动计时操作
isRunning = true;
if (_needInit) {
_timeInit = _calculateMillSecond(DateTime.now());
_needInit = false;
} else {
updatePauseTime();
}
timer = Timer.periodic(Duration(milliseconds: 17), _updateTime);
}
setState(() {

});
}

// 计次操作
void lap() {
// 获取调用瞬间的时间
var now = DateTime.now();
if (!isRunning) {
restartTime();
return;
}

laps.add(DateTime.fromMillisecondsSinceEpoch(
_calculateMillSecond(now) - _timeInit - _fixedDelta - _pauseTime));
if (laps.length > 1) {
_deltaList.add(_calculateMillSecond(laps[laps.length - 1]) -
_calculateMillSecond(laps[laps.length - 2]));
}
// 遍历时间差值list, 获取最大最小的值的索引
if (_deltaList.length > 1) {
_minIndex = 0;
_maxIndex = 0;
for (int i = 0; i < _deltaList.length; i++) {
if (_deltaList[_maxIndex] < _deltaList[i]) {
_maxIndex = i;
}
if (_deltaList[_minIndex] > _deltaList[i]) {
_minIndex = i;
}
}
}
}

// 将当前时间换算为mill second
int _calculateMillSecond(DateTime dt) {
return dt.hour * 3600000 + dt.minute * 60000 + dt.second * 1000 + dt.millisecond;
}

// 将mill second换算为时间
String _calculateTime() {
if (_needInit) {
return "00:00:00.000";
} else {
return _formatDateTime(DateTime.fromMillisecondsSinceEpoch(
_calculateMillSecond(DateTime.now()) - _timeInit - _fixedDelta - _pauseTime));
}
}
// 格式化输出DateTime, 主要进行的是补0操作
String _formatDateTime(DateTime dt) {
return "${dt.hour > 9 ? dt.hour : "0${dt.hour}"}:"
"${dt.minute > 9 ? dt.minute : "0${dt.minute}"}:"
"${dt.second > 9 ? dt.second : "0${dt.second}"}."
"${dt.millisecond > 99 ? dt.millisecond : "${dt.millisecond > 9 ? "0${dt.millisecond}" : "00${dt.millisecond}"}"}";
}

// 对极值的索引反馈不同的索引颜色
Color _matchColor(int index) {
if (index == _maxIndex) {
return Colors.lightGreen;
}
if (index == _minIndex) {
return Colors.amber;
}
return Colors.grey;
}
}

0x04 小结

虽然这只是一个非常非常简单的小应用,但是因为尼禄祭的原因,我居然还是花了大半天的时间才写完.
因为没有专门的学过Dart,只是把Dart当作Java合Kotlin的结合产物, 对它的语法有一丁点了解,所以可以磕磕碰碰的写出这个Demo.
Dart中没有Thread让我很难受,一开始都不知道怎么动态的更新时间,以前用Future配合一个while循环实现过一次,结果写这个Demo的时候,忘记怎么写了.
GitHub地址

0x05 参考资料

阅读量: