Flutter 初めてのアプリ作成 ストップウォッチ

今回はFlutterで簡単なストップウォッチを作成してみました。

環境

  • OS : Windous10
  • Flutter : v2.2.3

完成品

0秒からではなく、どの時間からでも開始できるストップウォッチにしてみました。
ストップウォッチを使っていて、途中から始めたい事がよくあったので、、、
機能は3つです。

1. 開始時間設定

f:id:wood__stock:20210812105432g:plain

2. ストップウォッチ開始、停止

f:id:wood__stock:20210812105456g:plain

3. リセット

f:id:wood__stock:20210812105533g:plain

Flutter習得

習得にはKBOYさんが運営しているFlutter大学の動画を見させていただきました。
環境構築から、簡単なYoutube風アプリ(側だけ)の作成まで、動画を見ながら2日で実施出来ました。
※2020年の動画で少し古いですが動作しました。

ストップウォッチ作成

1日でサクッと作成しました。
FlutterPicker というプラグインを使用すると、時間設定が楽に作成できます。
タイマー部分は、Timerで1秒の定期実行でカウントアップの関数を呼び出しています。
画面の更新は、SetStateではなくProviderパッケージを使い、notifyListeners()で変更を通知しています。

Widgetのツリー

f:id:wood__stock:20210812111532p:plain

ソースコード

/*
main.dart
メイン画面
*/

import 'package:flutter/material.dart';
import 'package:flutter_picker/Picker.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';

import 'main_model.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'ストップウォッチ',
      home: ChangeNotifierProvider<MainModel>(
        create: (context) => MainModel(),
        child: Scaffold(
          appBar: AppBar(
            title: Text('ストップウォッチ'),
          ),
          body: Consumer<MainModel>(
            builder: (context, model, child) {
              return Center(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    TextButton( // タイマー時間表示 + Editボタン
                      onPressed: () {
                        if (!model.isEditTimeEnabled) return;
                        // 時間設定ウインド表示
                        Picker(
                          adapter: DateTimePickerAdapter(
                            type: PickerDateTimeType.kHMS,
                            value: model.timerTime,
                            customColumnType: [3, 4, 5],
                          ),
                          title: Text("Select Time"),
                          onConfirm: (Picker, List values) {
                            model.changeTimerTime(DateTime.utc(
                                0, 0, 0, values[0], values[1], values[2]));
                          },
                        ).showModal(context);
                      },
                      child: Text(DateFormat.Hms().format(model.timerTime),
                          style: TextStyle(
                            fontSize: 80,
                          )),
                    ),
                    Row( // START,STOP,RESETボタン
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Padding( // STARTボタン
                          padding: const EdgeInsets.all(8.0),
                          child: ElevatedButton.icon(
                              onPressed: !model.isStartEnabled
                                  ? null
                                  : () {
                                      model.startTimer();
                                    },
                              icon: Icon(Icons.play_arrow),
                              label: Text('START')),
                        ),
                        Padding( // STOPボタン
                          padding: const EdgeInsets.all(8.0),
                          child: ElevatedButton.icon(
                              onPressed: !model.isStopEnabled
                                  ? null
                                  : () {
                                      model.stopTimer();
                                    },
                              icon: Icon(Icons.stop),
                              label: Text('STOP')),
                        ),
                        Padding( // RESETボタン
                          padding: const EdgeInsets.all(8.0),
                          child: ElevatedButton.icon(
                              onPressed: !model.isResetEnabled
                                  ? null
                                  : () {
                                      model.resetTimerTime();
                                    },
                              icon: Icon(Icons.clear),
                              label: Text('RESET')),
                        )
                      ],
                    ),
                  ],
                ),
              );
            },
          ),
        ),
      ),
    );
  }
}
/*
main_model.dart
メイン画面のデータモデル
*/

import 'dart:async';

import 'package:flutter/material.dart';

class MainModel extends ChangeNotifier {
  Timer? timer;
  DateTime timerTime = DateTime(0);
  bool isStartEnabled = true;
  bool isStopEnabled = false;
  bool isResetEnabled = true;
  bool isEditTimeEnabled = true;

  // 開始時間変更
  void changeTimerTime(DateTime time) {
    timerTime = time;
    notifyListeners();
  }

  // 開始時間リセット
  void resetTimerTime() {
    timerTime = new DateTime(0);
    notifyListeners();
  }

  // ストップウォッチ開始
  void startTimer() {
    if (timer == null) {
      timer = Timer.periodic(Duration(seconds: 1), (timer) {
        countUpTime(1);
      });
    }

    isStartEnabled = false;
    isStopEnabled = true;
    isResetEnabled = false;
    isEditTimeEnabled = false;
    notifyListeners();
  }

  // ストップウォッチ停止
  void stopTimer() {
    timer!.cancel();
    timer = null;
    notifyListeners();

    isStartEnabled = true;
    isStopEnabled = false;
    isResetEnabled = true;
    isEditTimeEnabled = true;
    notifyListeners();
  }

  // 1s カウントアップ
  void countUpTime(int second) {
    timerTime = timerTime.add(Duration(seconds: second));
    notifyListeners();
  }
}

参考