🧭

flutterには、ストップウォッチのパッケージがあった!

2023/10/28に公開

Overview

Flutterでストップウォッチを作ってみたことあるんですけど、パッケージを使うと作りやすくなるみたいです!
https://pub.dev/packages/stop_watch_timer/versions
今回は、flutter_hooksを使って状態管理をして、ストップウォッチの機能を作ってみました!

summary

今回書いたソースコードですが、ずっと時間が回ってるのでStreamを使うようです。
flutter_hooksを使うので、useStreamを使用してリアルタイムに時間の動きを監視するのをやってみました。

全体のコード
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:stop_watch_timer/stop_watch_timer.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const CountUpPage(),
    );
  }
}

class CountUpPage extends HookWidget {
  const CountUpPage({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    // useMemoizedを使うことで、再描画時にインスタンスが作成されるのを防ぐ
    final _stopWatchTimer = useMemoized(() => StopWatchTimer());
    // useScrollControllerでスクロールコントローラーを作成
    final _scrollController = useScrollController();
    // rawTimeは、経過時間をミリ秒で取得できる
    final rawTime = useStream<int>(
      _stopWatchTimer.rawTime,
      initialData: _stopWatchTimer.rawTime.value,
    );
    /* recordsは、ラップタイムのリストを取得できる
    タイマーが動いている時に、ボタンを押すとリストに時間が追加される
    */
    final records = useStream<List<StopWatchRecord>>(
      _stopWatchTimer.records,
      initialData: const [],
    );
    // useEffectで、画面が閉じられた時にタイマーを破棄する
    useEffect(() {
      return () {
        _stopWatchTimer.dispose();
        _scrollController.dispose();
      };
    }, []);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Stop Watch'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Center(
              child: SizedBox(
                width: 144,
                // rawTimeは、経過時間をを表示
                child: Text(
                  StopWatchTimer.getDisplayTime(rawTime.data!),
                  style: const TextStyle(fontSize: 24),
                ),
              ),
            ),
            const SizedBox(height: 32),
            SizedBox(
              height: 80,
              child: records.data!.isEmpty
                  ? const Text('No Record')
                  : ListView.builder(
                controller: _scrollController,
                itemCount: records.data!.length,// records.data!.lengthは、ラップタイムの数
                itemBuilder: (BuildContext context, int index) {
                  final data = records.data![index];// records.data![index]は、index番目のラップタイム
                  return Column(
                    children: [
                      Padding(
                        padding: const EdgeInsets.all(8),
                        // data.displayTimeは、ラップタイムを表示
                        child: Text('${index + 1} ${data.displayTime}'),
                      ),
                    ],
                  );
                },
              ),
            ),
            const SizedBox(height: 32),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: [
                // タイマーが動いている時に、ボタンを押すとリストに時間が追加される
                ElevatedButton(
                  onPressed: _stopWatchTimer.onStartTimer,
                  child: const Text('Start'),
                ),
                // タイマーが動いている時に、ボタンを押すとリストに時間が追加される
                ElevatedButton(
                  onPressed: _stopWatchTimer.onStopTimer,
                  child: const Text('Stop'),
                ),
                // タイマーが動いている時に、ボタンを押すとリストに時間が追加される
              ],
            ),
            const SizedBox(height: 32),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: [
                ElevatedButton(
                  onPressed: _stopWatchTimer.onResetTimer,
                  child: const Text('Reset'),
                ),
                // タイマーが動いている時に、ボタンを押すとリストに時間が追加される
                ElevatedButton(
                  onPressed: () async {
                    // タイマーが動いていない時に、ボタンを押すとリストに時間が追加される
                    if (!_stopWatchTimer.isRunning) {
                      return;
                    }
                    // タイマーが動いている時に、ボタンを押すとリストに時間が追加される
                    _stopWatchTimer.onAddLap();
                    // リストに時間が追加された時に、スクロールする
                    await Future<void>.delayed(const Duration(milliseconds: 100));
                    await _scrollController.animateTo(
                      _scrollController.position.maxScrollExtent,// スクロールする位置
                      duration: const Duration(milliseconds: 200),// 200ミリ秒かけてスクロールする
                      curve: Curves.easeOut,
                    );
                  },
                  child: const Text('Lap'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

こんな感じの動きをします

初期状態

スタートして、途中で記録する機能を使う

リセットする

thoughts

ストップウォッチの機能を実装するパッケージを使ってみた感想ですが、「こんな便利な機能あるのか〜」と知ってからは自作しないでパッケージを使いたいと思いましたね。
定期的にメンテナンスもされているようですし、問題ないのではないかと思いました。

Discussion