본문 바로가기
프로그래밍/Flutter & Dart

Flutter로 타이머앱 만들기

by 어느덧중반 2021. 8. 30.
반응형

구현해야 할 것들

- 0.01초 단위로 늘어나는 타이머

- 기록 버튼을 눌렀을 때 저장되는 순간 시간

- 시작/일시정지 버튼

- 시간 초기화 버튼


1. UI 만들기

- 시간을 나타내는 부분은 Text위젯이 필요 (second, millisecond)

- 저장되는 순간 시간이 보여질 ListView 위젯

- 시작/일시정지 버튼, 시간초기화 버튼, 기록 버튼

 

2. 기본 화면 구성 (앱바, 바텀네비게이션바, 플로팅액션버튼)

import 'package:flutter/material.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: 'Stop watch',
      theme: ThemeData(
        primarySwatch: Colors.lightGreen,
      ),
      home: StopWatchPage(),
    );
  }
}

class StopWatchPage extends StatefulWidget {
  @override
  _StopWatchPageState createState() => _StopWatchPageState();
}

class _StopWatchPageState extends State<StopWatchPage> {
  var _icon = Icons.play_arrow;
  var _color = Colors.amber;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Stop watch'),
      ),
      body: _body(),
      bottomNavigationBar: BottomAppBar(
        child: Container(
          height: 80
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => setState(() {
          _click();
        }),
        child: Icon(_icon),
        backgroundColor: _color,
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
    );
  }

  // 시간 보여지는 부분
  Widget _body() {
    return Container();
  }

  // 시작, 일시정지 버튼
  void _click() {
    if(_icon == Icons.play_arrow) {
      _icon = Icons.pause;
      _color = Colors.grey;
    } else {
      _icon = Icons.play_arrow;
      _color = Colors.amber;
    }
  }
}

3. 버튼추가, Text위젯 추가

  // 시간 보여지는 부분
  Widget _body() {
    return Center(
      child: Padding(
        padding: const EdgeInsets.all(40),
        child: Stack( // 위젯 위에 위젯 겹치기 가능
          children: <Widget>[
            Column(
              children: <Widget>[
                Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  crossAxisAlignment: CrossAxisAlignment.end,
                  children: <Widget>[
                    Text( // seconds 영역
                      '00',
                      style: TextStyle(fontSize: 80),
                    ),
                    Text( // milliseconds 영역
                      '.99',
                      style: TextStyle(fontSize: 30),
                    )
                  ],
                ),
                SizedBox(height: 50, // 여백
                ),
                Container(
                  width: 200,
                  height: 300,
                  child: ListView(
                    children: <Widget>[
                      Text('111111111'),
                      Text('222222222'),
                    ],
                  ),
                )
              ],
            ),
            makeGradientButton('Clear Board', 80),
            makeGradientButton('Save Time!!', 20),
          ],
        ),
      ),
    );
  }
  Widget makeGradientButton(String btnText, double _height) {
    return Positioned(
      right: 0,
      bottom: _height,
      child: RaisedButton(
        onPressed: () {},
        textColor: Colors.white,
        padding: const EdgeInsets.all(0.0),
        child: Container(
          decoration: const BoxDecoration(
            gradient: LinearGradient(
              colors: <Color>[
                Color(0xFF0D47A1),
                Color(0xFF1976D2),
                Color(0xFF42A5F5),
              ],
            ),
          ),
          padding: const EdgeInsets.all(10.0),
          child: Text(
            btnText,
            style: TextStyle(
                fontSize: 20),
          ),
        ),
      ),
    );
  }

 

4. 타이머 구현하기

  • Timer 클래스
  • 시작/일시정지 상태변환, 동작
  • 시간 표시
  • 초기화

 

  • Timer 클래스 : dart:async 패키지에 포함. 일정간격동안 반복하여 동작 수행할 때 사용
import 'dart:async';

...

Timer.periodic(Duration(milliseconds: 10), (timer) { // Duration은 일정 간격이 들어가며 시분초 등이 들어갈 수 있다.
	// To Do
}

 

구현에 필요한 변수

Timer _timer; // 타이머
var _time = 0; // 실제 늘어날 시간
var _isPlaying = false; // 시작/정지 상태값
List<String> _saveTimes = []; // 기록하기 위한 리스트

Timer앱을 종료할 때는 반복되는 동작을 멈춰줘야 한다. dispose() 메소드를 재정의하여 타이머 취소되도록 설정해줘야 한다.

@override
dispose() {
	_timer?.cancel(); // _timer가 null이 아니면 cancel() (null이면 그냥 아무것도 안함)
    super.dispose();
}

 

최종 소스

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

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Stop watch',
      theme: ThemeData(
        primarySwatch: Colors.lightBlue,
      ),
      home: StopWatchPage(),
    );
  }
}

class StopWatchPage extends StatefulWidget {
  @override
  _StopWatchPageState createState() => _StopWatchPageState();
}

class _StopWatchPageState extends State<StopWatchPage> {
  var _icon = Icons.play_arrow;
  var _color = Colors.amber;

  Timer _timer; // 타이머
  var _time = 0; // 실제 늘어날 시간
  var _isPlaying = false; // 시작/정지 상태값
  List<String> _saveTimes = []; // 기록하기 위한 리스트

  @override
  void dispose() {
    _timer?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: Text('Stop watch'),
      ),
      body: _body(),
      bottomNavigationBar: BottomAppBar(
        child: Container(
          height: 80
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => setState(() {
          _click();
        }),
        child: Icon(_icon),
        backgroundColor: _color,
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
    );
  }

  // 시간 보여지는 부분
  Widget _body() {
    var sec = _time ~/ 100; // 초
    var hundredth = '${_time % 100}'.padLeft(2, '0');

    return Center(
      child: Padding(
        padding: const EdgeInsets.all(40),
        child: Stack( // 위젯 위에 위젯 겹치기 가능
          children: <Widget>[
            Column(
              children: <Widget>[
                Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  crossAxisAlignment: CrossAxisAlignment.end,
                  children: <Widget>[
                    Text( // seconds 영역
                      '$sec',
                      style: TextStyle(fontSize: 80),
                    ),
                    Text( // milliseconds 영역
                      '.$hundredth',
                      style: TextStyle(fontSize: 30),
                    )
                  ],
                ),
                SizedBox(height: 50, // 여백
                ),
                Container(
                  width: 200,
                  height: 300,
                  child: ListView(
                    children: _saveTimes.map((time) => Text(time, style: TextStyle(fontSize: 20),)).toList(),
                  ),
                )
              ],
            ),
            Positioned(
              right: 0,
              bottom: 80,
              child: RaisedButton(
                onPressed: () {
                  setState(() {
                    _reset();
                  });
                },
                textColor: Colors.white,
                padding: const EdgeInsets.all(0.0),
                child: Container(
                  decoration: const BoxDecoration(
                    gradient: LinearGradient(
                      colors: <Color>[
                        Color(0xFF0D47A1),
                        Color(0xFF1976D2),
                        Color(0xFF42A5F5),
                      ],
                    ),
                  ),
                  padding: const EdgeInsets.all(10.0),
                  child: Text(
                    'Clear Board',
                    style: TextStyle(
                        fontSize: 20),
                  ),
                ),
              ),
            ),
            Positioned(
              right: 0,
              bottom: 20,
              child: RaisedButton(
                onPressed: () {
                  setState(() {
                    _saveTime('$sec.$hundredth');
                  });
                },
                textColor: Colors.white,
                padding: const EdgeInsets.all(0.0),
                child: Container(
                  decoration: const BoxDecoration(
                    gradient: LinearGradient(
                      colors: <Color>[
                        Color(0xFF0D47A1),
                        Color(0xFF1976D2),
                        Color(0xFF42A5F5),
                      ],
                    ),
                  ),
                  padding: const EdgeInsets.all(10.0),
                  child: Text(
                    'Save Time!!',
                    style: TextStyle(
                        fontSize: 20),
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
  // 시작, 일시정지 버튼
  void _click() {
    _isPlaying = !_isPlaying;

    if(_isPlaying) {
      _icon = Icons.pause;
      _color = Colors.grey;
      _start();
    } else {
      _icon = Icons.play_arrow;
      _color = Colors.amber;
      _pause();
    }
  }

  // 1/100초에 한 번씩 time 1씩 증가
  void _start() {
    _timer = Timer.periodic(Duration(milliseconds: 10), (timer) {
      setState(() {
        _time++;
      });
    });
  }

  // 타이머 중지(취소)
  void _pause() {
    _timer?.cancel();
  }

  // 초기화
  void _reset() {
    setState(() {
      _isPlaying = false;
      _timer?.cancel();
      _saveTimes.clear();
      _time = 0;
    });
  }

  // 기록하기
  void _saveTime(String time) {
    _saveTimes.insert(0, '${_saveTimes.length + 1}등 : $time');
  }
}

시작/일시정지/시간기록
리스트뷰에 시간기록/초기화

반응형

댓글