posth2071/StopWatch_Kotlin
Contribute to posth2071/StopWatch_Kotlin development by creating an account on GitHub.
github.com
timer 기본 사용방법
반복주기는
peroid 프로퍼티
로 설정, 단위는
1000분의 1초 (period = 1000, 1초)
timer(preoid = 1000){
// 1초마다 실행할 블록
// 백그라운드로 실행되는 부분, UI조작 X
timer 블록 내부는 워커스레드(백그라운드) 공간이기에 runOnUiThread() 메서드를 통해 UI조작
: 시간이 오래걸릴 수 있는 로직은 워커스레드 공간에서 처리하고 UI조작만 전달
timer(preoid = 1000){
// 오래 걸리는 작업 수행부분
runOnUiThread {
// UI 조작 로직
메서드 구현
start() 함수 - 시작
start버튼을 누르면 호출되는 메서드
타이머를 시작하고 0.01초마다 화면에 시간을 갱신하는 로직, runOnUiThread()로 화면 시간 갱신
private fun start() {
fab_start.setImageResource(R.drawable.ic_pause_black_24dp) // 시작버튼을 일시정지 이미지로 변경
timerTask = kotlin.concurrent.timer(period = 10) { // timer() 호출
time++ // period=10, 0.01초마다 time를 1씩 증가
val sec = time / 100 // time/100, 나눗셈의 몫 (초 부분)
val milli = time % 100 // time%100, 나눗셈의 나머지 (밀리초 부분)
// UI조작을 위한 메서드
runOnUiThread {
secText.text = "$sec" // TextView 세팅
milliText.text = "$milli" // Textview 세팅
pause() 함수 - 일시정지
타이머를 일시정지하는 함수 (초기화 X)
현재 timerTask가 진행중인지 체크 한 뒤, 진행 중이라면 cancel() 메서드를 호출해 timer를 정지
안전한 호출(?.)을 통해 간단하게 timerTask 상태를 처리가능
private fun pause() {
fab_start.setImageResource(R.drawable.ic_play) // 일시정지 아이콘에서 start아이콘으로 변경
timerTask?.cancel(); // 안전한 호출(?.)로 timerTask가 null이 아니면 cancel() 호출
lapTime() 함수 - 시간 기록
현재 timer의 시간을 기록하는 함수,
ScrollView내부에 선언한 LinearLayout(Vertical 방향)에 최상단으로(index 0) TextView를 추가하는 방식
기록버튼을 클릭 시 Timer가 진행 중인 상태라면 기록 저장, 아니라면 저장 X
// 기록버튼 클릭리스너 등록
btn_lab.setOnClickListener {
if(time!=0) lapTime() // 시간 저장변수 time이 0이라면 함수호출하지 않음
기록 저장
: TextView를 동적으로 생성해서 LinearLayout에 추가하는 방법
apply() 함수로 TextView 선언과 동시에 초기화
private fun lapTime() {
val lapTime = time // 함수 호출 시 시간(time) 저장
// apply() 스코프 함수로, TextView를 생성과 동시에 초기화
val textView = TextView(this).apply {
setTextSize(20f) // fontSize 20 설정
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 세팅
fab_start.setImageResource(R.drawable.ic_play) // start아이콘 설정
secText.text = "0" // TextView 초기화
milliText.text = "00"
lap_Layout.removeAllViews() // Layout에 추가한 기록View 모두 삭제
index = 1
전체 코드 Activity
class MainActivity : AppCompatActivity() {
private var time = 0
private var isRunning = false
private var timerTask: Timer? = null
private var index :Int = 1
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
fab_start.setOnClickListener {
isRunning = !isRunning
if (isRunning) start() else pause()
fab_reset.setOnClickListener {
reset()
btn_lab.setOnClickListener {
if(time!=0) lapTime()
private fun start() {
fab_start.setImageResource(R.drawable.ic_pause_black_24dp)
timerTask = kotlin.concurrent.timer(period = 10) {
time++
val sec = time / 100
val milli = time % 100
runOnUiThread {
secText.text = "$sec"
milliText.text = "$milli"
private fun pause() {
fab_start.setImageResource(R.drawable.ic_play)
timerTask?.cancel();
private fun reset() {
timerTask?.cancel()
time = 0
isRunning = false
fab_start.setImageResource(R.drawable.ic_play)
secText.text = "0"
milliText.text = "00"
lap_Layout.removeAllViews()
index = 1
private fun lapTime() {
val lapTime = time
val textView = TextView(this).apply {
setTextSize(20f)
textView.text = "${lapTime / 100}.${lapTime % 100}"
lap_Layout.addView(textView,0)
index++
Layout XML
<?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=".MainActivity">
<Button
android:id="@+id/btn_lab"
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" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_start"
android:layout_width="56dp"
android:layout_height="wrap_content"
android:backgroundTint="#5DB160"
android:clickable="true"
android:tint="#FFFFFF"
app:layout_constraintBottom_toBottomOf="@+id/btn_lab"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/btn_lab"
app:layout_constraintVertical_bias="0.0"
app:srcCompat="@drawable/ic_play" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_reset"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:backgroundTint="#D64D7C"
android:clickable="true"
android:tint="#FFFFFF"
app:layout_constraintBottom_toBottomOf="@+id/fab_start"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.12"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/btn_lab"
app:layout_constraintVertical_bias="1.0"
app:srcCompat="@drawable/ic_refresh" />
<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/fab_start"
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>