안드로이드 코틀린 개발하기 - StopWatch

사전 설정

Design 라이브러리 추가하기

FloatingActioinButton을 사용하기 위해 design 라이브러리를 추가해야 합니다.
다음과 같은 코드를 삽입해줍니다.

build.gradle (Module: app)

1
implementation 'com.android.support:design:28.0.0'

벡터 드로어블 사용 환경 설정하기

build.gradle (Module: app)

버전 5.0미만의 기기에서 벡터 이미지를 사용하기 위해서 아래와 같은 코드를 삽입해줍니다.

1
vectorDrawables.useSupportLibrary = true

레이아웃 만들기

가장 최근에 등장했고, 현재 안드로이드 기본 레이아웃인 Constrain Layout을 사용합니다.
초 단위와 밀리초 단위를 보여줄 TextView를 사용합니다.
랩 타임을 기록할 이벤트를 발생시킬 Button을 사용합니다.
스탑워치의 시작, 정지, 리셋 이벤트를 발생시킬 FloatingActionButton을 사용합니다.
랩 타임이 기록되어 보여질 ScrollView를 사용합니다.

FloatingActionButton에 사용될 이미지는 에셋 스튜디오에서 벡터 이미지를 생성하여 사용합니다.

activity_main.xml

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
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<TextView
android:id="@+id/secondTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:textSize="100sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.1"/>

<TextView
android:id="@+id/milliSecondTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="00"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:textSize="22sp"
app:layout_constraintBaseline_toBaselineOf="@+id/secondTv"
app:layout_constraintStart_toEndOf="@+id/secondTv"/>

<android.support.design.widget.FloatingActionButton
android:id="@+id/playFab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:clickable="true"
android:tint="@android:color/white"
app:backgroundTint="@color/colorPrimary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:srcCompat="@drawable/ic_play_arrow_black_24dp"/>

<android.support.design.widget.FloatingActionButton
android:id="@+id/refreshFab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginStart="16dp"
android:clickable="true"
app:backgroundTint="@color/colorPrimary"
android:tint="@android:color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:srcCompat="@drawable/ic_refresh_black_24dp"/>

<Button
android:id="@+id/lapBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginEnd="16dp"
android:text="랩 타임"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>

<ScrollView
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toTopOf="@+id/lapBtn"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/secondTv">

<LinearLayout
android:id="@+id/lapLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"/>
</ScrollView>

</android.support.constraint.ConstraintLayout>

타이머 구현하기(Timer)

timer 사용하기

코틀린에서 타이머를 구현하는 방법은 다음과 같습니다.
타이머는 일정한 시간을 주기로 반복하는 동작을 수행할 때 사용합니다.

1
2
3
timer(period = 1000) { // 1초(1000ms)마다
// 수행할 동작
}

UI 변경하기

시간이 흘러감에 따라 흘러간 시간을 사용자에게 보여줘야합니다.

안드로이드에서 UI를 조작하는 것은 메인 스레드에서 해야 합니다.
오래 걸리는 작업을 할 때에는 보이지 않는 곳에서 처리하는 워커 스레드에서 합니다.
워커 스레드에서는 UI를 조작할 수 없습니다.
그리고 timer는 워커 스레드에서 수행되기 때문에 UI를 조작할 수 없습니다.
따라서, timer내부에 runOnUiThread() 메소드를 사용해서 UI를 조작해야 합니다.

1
2
3
4
5
timer(period = 1000) {
runOnUiThread {
// UI 조작
}
}

타이머 시작

타이머가 시작이 되면, 시작 버튼의 이미지가 바뀌게 됩니다.
그리고 0.01초 단위로 타이머를 작동시킵니다.
초와 밀리초 단위에 맞게 계산을 합니다.
UI를 조작하기 위해, runOnUiThread 안에서 TextView에 값을 뿌려줍니다.

1
2
3
4
5
6
7
8
9
10
11
12
private fun start() {
playFab.setImageResource(R.drawable.ic_pause_black_24dp)
timerTask = timer(period = 10) {
time++
val sec = time / 100
val milli = time % 100
runOnUiThread {
secondTv.text = "$sec"
milliSecondTv.text = "$milli"
}
}
}

타이머 일시정지

시작과 반대로, 일시정지가 되면 버튼의 이미지가 바뀌게 됩니다.
그리고 실행중인 타이머가 있다면, 타이머를 취소시킵니다.

1
2
3
4
private fun pause() {
playFab.setImageResource(R.drawable.ic_play_arrow_black_24dp)
timerTask?.cancel() // 실행중인 타이머가 있다면, 종료
}

타이머 초기화

타이머에 사용했던 모든 변수를 초기화해줍니다.

1
2
3
4
5
6
7
8
9
10
private fun reset() {
timerTask?.cancel()
time = 0
isRunning = false
playFab.setImageResource(R.drawable.ic_play_arrow_black_24dp)
secondTv.text = "0"
milliSecondTv.text = "00"
lapLayout.removeAllViews()
lap = 1
}

랩 타임 기록하기

동적으로 뷰 추가하기

랩 타임을 기록하는 버튼을 누르면, 스크롤뷰 내부에 있는 리니어 레이아웃 안에 텍스트 뷰가 추가가 되어야 합니다.
텍스트뷰 객체를 생성하고, 레이아웃에 추가해주면 됩니다.
리니어 레이아웃에 addView() 메서드를 사용하면 됩니다.
첫 번째 인자로 추가할 뷰를 넣습니다.
첫 번째 인자만 넣어도 되지만, 추가될 뷰가 레이아웃의 어느 위치에 위치할지 지정해주는 두 번째 인자도 설정할 수 있습니다.
두 번째 인자에 0을 넣으면, 레이아웃의 항상 최상단에 추가가 됩니다.

1
2
3
val textView = TextView(this)
textView.text = "글자"
lapLayout.addView(textView, 0)

랩 타임 표시하기

랩 타임을 표시하기 위해 버튼을 누르면, 시간을 받아옵니다.
그리고 텍스트뷰 객체를 생성하고, 받아온 시간을 설정해줍니다.
레이아웃에 텍스트뷰를 추가해줍니다.

1
2
3
4
5
6
7
8
private fun recordLapTime() {
val lapTime = this.time
val lapTv = TextView(this)
lapTv.text = "$lap LAB : ${lapTime / 100}.${lapTime % 100}"

lapLayout.addView(lapTv, 0) // 맨 위에 뷰를 추가한다.
lap++
}

소스 코드

Kotlin_Stop_Watch

레퍼런스

오준석의 안드로이드 생존코딩 코틀린 편 ( 오준석 저 / 한빛미디어 / 2018.10.01 )