progress로 시간을 설정하는 타이머를 만들어볼 것이다.
1 ~ 60분까지 타이머 설정하기
1초마다 화면 갱신하기
타이머 효과음 넣기
📌 알게 된 점
1. 함수로 빼내 무슨 역할을 하는지 추상화하는 것이 중요하다. 가독성 굿!
2. SoundPool로 효과음을 넣는다.
📌 최종 코드
MainActivity.kt
package com.example.timer
import android.media.SoundPool
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.CountDownTimer
import android.widget.SeekBar
import com.example.timer.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private val binding: ActivityMainBinding by lazy {
ActivityMainBinding.inflate(layoutInflater)
}
private var currentCountDownTimer: CountDownTimer? = null
private val soundPool = SoundPool.Builder().build()
private var tickingSoundId: Int? = null
private var bellSoundId: Int? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
bindViews()
initSounds() // 사운드 로드
}
override fun onResume() { // 다시 돌아왔을 경우
super.onResume()
soundPool.autoResume()
}
override fun onPause() { // 화면에 안 보일 경우
super.onPause()
soundPool.autoPause() // 모든 활성화된 소리 멈춤
}
override fun onDestroy() {
super.onDestroy()
soundPool.release() // 사운드 사용하지 않을 때 메모리 해제
}
private fun bindViews() {
binding.seekBar.setOnSeekBarChangeListener(
object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(p0: SeekBar?, p1: Int, p2: Boolean) {
if(p2) { // 사용자가 실제로 건드렸을 때만 업데이트
updateRemainTimes(p1 * 60 * 1000L)
}
}
override fun onStartTrackingTouch(p0: SeekBar?) {
stopCountDown()
}
override fun onStopTrackingTouch(p0: SeekBar?) {
binding.seekBar ?: return
if(binding.seekBar.progress == 0) {
stopCountDown()
} else {
startCountDown()
}
}
}
)
}
private fun initSounds() {
tickingSoundId = soundPool.load(this, R.raw.timer_ticking, 1)
bellSoundId = soundPool.load(this, R.raw.timer_bell, 1)
}
private fun createCountDownTimer(initialMillis: Long) =
object: CountDownTimer(initialMillis, 1000L) {
override fun onTick(p0: Long) {
updateRemainTimes(p0)
updateSeekBar(p0)
}
override fun onFinish() {
completeCountDown()
}
}
private fun startCountDown() {
currentCountDownTimer = createCountDownTimer(binding.seekBar.progress * 60 * 1000L)
// 바로 위에 생성해도 코드 사이에 변화가 생길 수 있기 때문에 nullable
currentCountDownTimer?.start()
// 인자가 nullable 할 경우, null 이 아닌 경우에만 let 으로
tickingSoundId?.let { soundId ->
soundPool.play(soundId, 1F, 1F, 0, -1, 1F)
}
}
private fun stopCountDown() {
currentCountDownTimer?.cancel()
currentCountDownTimer = null
soundPool.autoPause()
}
private fun completeCountDown() {
updateRemainTimes(0)
updateSeekBar(0)
soundPool.autoPause()
bellSoundId?.let { soundId ->
soundPool.play(soundId, 1F, 1F, 0, 0, 1F)
}
}
private fun updateRemainTimes(remainMillis: Long) {
val remainSeconds = remainMillis / 1000
binding.remainMinutesTextView.text = "%02d'".format(remainSeconds / 60)
binding.remainSecondsTextView.text = "%02d".format(remainSeconds % 60)
}
private fun updateSeekBar(remainMillis: Long) {
binding.seekBar.progress = (remainMillis / 1000 / 60).toInt()
}
}
activity_main.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">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/img_tomato_stem"
app:layout_constraintBottom_toTopOf="@id/remainMinutesTextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/remainMinutesTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="00'"
android:textColor="@color/white"
android:textSize="120sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/remainSecondsTextView"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/remainSecondsTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="00"
android:textColor="@color/white"
android:textSize="70sp"
android:textStyle="bold"
app:layout_constraintBaseline_toBaselineOf="@id/remainMinutesTextView"
app:layout_constraintBottom_toBottomOf="@id/remainMinutesTextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/remainMinutesTextView"
app:layout_constraintTop_toTopOf="@id/remainMinutesTextView" />
<SeekBar
android:id="@+id/seekBar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:max="60"
android:progressDrawable="@color/transparent"
android:thumb="@drawable/ic_thumb"
android:tickMark="@drawable/drawable_tick_mark"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/remainMinutesTextView" />
</androidx.constraintlayout.widget.ConstraintLayout>
'🍞 Front-End > Android' 카테고리의 다른 글
[Android] Retrofit2 사용법 (0) | 2022.11.27 |
---|---|
[Android] 데이터 저장 방법 (SharedPreferences, RoomDB) (0) | 2022.11.20 |
[Android] 앱 권한 요청 API level 오류 (0) | 2022.11.02 |
[Android] 계산기 RoomDB (0) | 2022.10.31 |
[Android] 에뮬레이터에서 Toast 메세지가 안 보일 때 (0) | 2022.10.31 |