はじめに
ひょんなことからFlutterでカウントダウンするコードを書くことになったのですが、色々なサンプル実装などを見ていて以下のような引っ掛かりがありました。
-
Timer
自体にはpauseするメソッドはない(カウントダウンのためのものではないため) -
AnimationBuilder
を使っているためフレームごとでリビルドが起きており、長時間を計測する場合だと動作が重くなる -
StreamBuilder
やらbloc
パターンを使って実装しているけど、なんだか複雑に実装しすぎている - かなり隅のコードなので
Provider
も使うのが億劫
ということで、諸々のパッケージを使わずにカウントダウンをなるべくシンプルに実装しました。
実装
留意点としては以下です。
-
Timer
自体には一時停止などの機能はないので、一時停止時には一旦破棄し、再生時に作り直す - 実際の計算をするのは
currentSeconds
の役割 -
currentSeconds
が更新されない場合はカウントは進まない - つまり、1秒以内の間隔で再生・停止を連打するとカウントは進まない
- これを進めたい場合は
milliseconds
単位で実装しなおせばよいですが、あまり現実的でないのと余計なリビルドの原因となるためしない方が良いと思います
count_hoge.dart
class CountHoge extends StatefulWidget {
CountHoge({Key key}) : super(key: key);
@override
_CountHogeState createState() => _CountHogeState();
}
class _CountHogeState extends State<CountHoge> {
Timer _timer;
int _currentSeconds;
@override
void dispose() {
super.dispose();
_timer.cancel();
}
@override
void initState() {
super.initState();
final workMinuts = 5;
_currentSeconds = workMinuts * 60;
_timer = countTimer();
}
Timer countTimer() {
return Timer.periodic(
const Duration(seconds: 1),
(Timer timer) {
if (_currentSeconds < 1) {
timer.cancel();
} else {
setState(() {
_currentSeconds = _currentSeconds - 1;
});
}
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
const SizedBox(height: 16),
_timeStr(),
const SizedBox(height: 16),
_panel(),
],
),
));
}
Widget _timeStr() {
return Text(
timerString(_currentSeconds),
style: TextStyle(fontSize: 32, color: Colors.black),
);
}
String timerString(int leftSeconds) {
final minutes = (leftSeconds / 60).floor().toString().padLeft(2, '0');
final seconds = (leftSeconds % 60).floor().toString().padLeft(2, '0');
return '$minutes:$seconds';
}
Widget _panel() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
GestureDetector(
onTap: () {
if (_timer.isActive) {
_timer.cancel();
}
},
child: SizedBox(height: 40, child: Icon(Icons.stop)),
),
GestureDetector(
onTap: () {
if (!_timer.isActive) {
setState(() {
_timer = countTimer();
});
}
},
child: SizedBox(height: 40, child: Icon(Icons.play_arrow))),
],
);
}
}