안녕하세요. 이번에는 코틀린을 통해 스톱워치를 구현 해보도록 하겠습니다.
우선 만들고자 하는 화면을 그려보도록 하겠습니다. 화면구성은 다음과 같습니다.
스톱워치를 컨트롤 할 수 있는 시작과 초기화 버튼과 각 기록을 화면에 표시하는 기록하기 버튼 그리고 각 시간을 보여주는 TextView를 준비해둡니다.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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=".StopWatchActivity">
<Button
android:id="@+id/resetBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:tint="#FFFFFF"
android:text="초기화"
app:layout_constraintBottom_toBottomOf="@+id/startBtn"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.12"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/recordBtn"
app:layout_constraintVertical_bias="1.0"/>
<Button
android:id="@+id/startBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="시작"
android:clickable="true"
android:tint="#FFFFFF"
app:layout_constraintBottom_toBottomOf="@+id/recordBtn"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/recordBtn"
app:layout_constraintVertical_bias="0.0" />
<Button
android:id="@+id/recordBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="기록하기"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.89"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.96" />
<TextView
android:id="@+id/secText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:freezesText="false"
android:text="0"
android:textAllCaps="false"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:textSize="100sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.100000024" />
<TextView
android:id="@+id/milliText"
android:layout_width="91dp"
android:layout_height="30dp"
android:layout_marginStart="8dp"
android:layout_marginBottom="20dp"
android:text="TextView"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
app:layout_constraintBottom_toBottomOf="@+id/secText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/secText" />
<ScrollView
android:id="@+id/scroll1"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toTopOf="@+id/startBtn"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/secText">
<LinearLayout
android:id="@+id/lap_Layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical" />
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
이제 이를 어떻게 구현하는지 알아보도록 하겠습니다.
우선 타이머, 스톱워치 등을 구현하기 위해서는 'Thread'라는 녀석을 알아야 합니다. Thread란 프로세스(process) 내에서 실제로 작업을 수행하는 주체를 의미합니다. 즉 시간초를 세어주는 작업을 메인스레드(UI스레드)가 아닌 다른 스레드(워크 스레드, 백그라운드 스레드)로 동작시켜야 합니다.
또한 여기서 생각을 하셔야 하는게 백그라운드 스레드(워커 스레드)에서 동작하는 건 UI에 직접 반영할 수 없습니다. 이를 해결하는 방법으로 Handler를 호출 및 runOnUiThread() 함수를 호출하는 방법 존재합니다.
자세한 내용은 개발자 사이트를 통해 알아보도록 합시다.
developer.android.com/reference/android/os/Handler
developer.android.com/reference/android/app/Activity
동작하는 방식은 다음과 같습니다.
start() 함수 - 시작
start버튼을 누르면 호출되는 메서드입니다.
시간초는 0.01초마다 갱신되며 방법은 runOnUiThread()로 구현하였습니다.
private fun start() {
startBtn.text ="중지"
timerTask = kotlin.concurrent.timer(period = 10) { //반복주기는 peroid 프로퍼티로 설정, 단위는 1000분의 1초 (period = 1000, 1초)
time++ // period=10으로 0.01초마다 time를 1씩 증가하게 됩니다
val sec = time / 100 // time/100, 나눗셈의 몫 (초 부분)
val milli = time % 100 // time%100, 나눗셈의 나머지 (밀리초 부분)
// UI조작을 위한 메서드
runOnUiThread {
secText.text = "$sec"
milliText.text = "$milli"
}
}
}
pause() 함수 - 일시정지
타이머를 일시정지합니다
현재 timerTask가 진행중인지 체크 한 뒤, 진행 중이라면 cancel() 메서드를 호출해 timer를 정지하게 됩니다.
private fun pause() {
startBtn.text ="재실행"
timerTask?.cancel();
}
lapTime() 함수 - 시간 기록
현재 timer의 시간을 기록하는 함수입니다.
ScrollView내부에 선언한 LinearLayout(Vertical 방향)에 최상단으로(index 0) TextView를 추가하는 방식
기록버튼을 클릭 시 Timer가 진행 중인 상태라면 기록 저장하게 됩니다.
private fun lapTime() {
val lapTime = time // 함수 호출 시 시간(time) 저장
// apply() 스코프 함수로, TextView를 생성과 동시에 초기화
val textView = TextView(this).apply {
setTextSize(20f) // fontSize 20 설정
}
textView.text = "${lapTime / 100}.${lapTime % 100}" // 출력할 시간 설정
lap_Layout.addView(textView,0) // layout에 추가, (View, index) 추가할 위치(0 최상단 의미)
index++ // 추가된 View의 개수를 저장하는 index 변수
}
reset() 함수 - 초기화
Timer 기록을 초기화 하는 함수
time(시간), index(기록 개수), timerTask(타이머 객체), TextView(UI초기화), layout(추가된 기록View 모두 제거)
private fun reset() {
timerTask?.cancel() // timerTask가 null이 아니라면 cancel() 호출
time = 0 // 시간저장 변수 초기화
isRunning = false // 현재 진행중인지 판별하기 위한 Boolean변수 false 세팅
secText.text = "0" // 시간(초) 초기화
milliText.text = "00" // 시간(밀리초) 초기화
startBtn.text ="시작"
lap_Layout.removeAllViews() // Layout에 추가한 기록View 모두 삭제
index = 1
}
class StopWatchActivity : AppCompatActivity() {
private var time = 0
private var isRunning = false
private var timerTask: Timer? = null
private var index :Int = 1
private lateinit var secText: TextView
private lateinit var milliText: TextView
private lateinit var startBtn: Button
private lateinit var resetBtn: Button
private lateinit var recordBtn: Button
private lateinit var lap_Layout: LinearLayout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_stop_watch)
//View inflate
secText = findViewById(R.id.secText)
milliText = findViewById(R.id.milliText)
startBtn = findViewById(R.id.startBtn)
resetBtn = findViewById(R.id.resetBtn)
recordBtn = findViewById(R.id.recordBtn)
lap_Layout = findViewById(R.id.lap_Layout)
//버튼 클릭 리스너
startBtn.setOnClickListener {
isRunning = !isRunning
if (isRunning) start() else pause()
}
resetBtn.setOnClickListener {
reset()
}
recordBtn.setOnClickListener {
if(time!=0) lapTime()
}
}
private fun start() {
startBtn.text ="중지"
timerTask = kotlin.concurrent.timer(period = 10) { //반복주기는 peroid 프로퍼티로 설정, 단위는 1000분의 1초 (period = 1000, 1초)
time++ // period=10으로 0.01초마다 time를 1씩 증가하게 됩니다
val sec = time / 100 // time/100, 나눗셈의 몫 (초 부분)
val milli = time % 100 // time%100, 나눗셈의 나머지 (밀리초 부분)
// UI조작을 위한 메서드
runOnUiThread {
secText.text = "$sec"
milliText.text = "$milli"
}
}
}
private fun pause() {
startBtn.text ="재실행"
timerTask?.cancel();
}
private fun reset() {
timerTask?.cancel() // timerTask가 null이 아니라면 cancel() 호출
time = 0 // 시간저장 변수 초기화
isRunning = false // 현재 진행중인지 판별하기 위한 Boolean변수 false 세팅
secText.text = "0" // 시간(초) 초기화
milliText.text = "00" // 시간(밀리초) 초기화
startBtn.text ="시작"
lap_Layout.removeAllViews() // Layout에 추가한 기록View 모두 삭제
index = 1
}
private fun lapTime() {
val lapTime = time // 함수 호출 시 시간(time) 저장
// apply() 스코프 함수로, TextView를 생성과 동시에 초기화
val textView = TextView(this).apply {
setTextSize(20f) // fontSize 20 설정
}
textView.text = "${lapTime / 100}.${lapTime % 100}" // 출력할 시간 설정
lap_Layout.addView(textView,0) // layout에 추가, (View, index) 추가할 위치(0 최상단 의미)
index++ // 추가된 View의 개수를 저장하는 index 변수
}
}
스틱코드를 이용하면 아래 코드를 언제든지 자유롭게 가져다 사용할 수 있습니다.
stickode.com/detail.html?no=2152
'안드로이드 코틀린' 카테고리의 다른 글
[Kotlin][Android] Data Bindng을 이용한 계산기 어플 만들기 (0) | 2021.06.20 |
---|---|
[Kotlin][Android] 달력 만들기 (1) | 2021.06.17 |
[Kotlin][Android] 커스텀 다이얼로그 빠르게 만들기 (0) | 2021.06.05 |
[Kotlin][Android] 그래프 만들기 (6) | 2021.06.04 |
[Kotlin][Android] SST(SpeechToText) 기능 구현하기 (0) | 2021.05.31 |