Project/๊ทธ ์ธ
๐ PomodoroTimer : ํ์ด๋จธ ์ดํ๋ฆฌ์ผ์ด์
DevGoni
2022. 5. 13. 17:53
728x90
๐ PomodoroTimer
º ํ๋ก์ ํธ ์ธ์ : 1๋ช
º ํ๋ก์ ํธ ๊ธฐ๊ฐ : 2022์ 3์ 17์ผ / 1์ผ
๐ ๊ฐ๋ฐํ๊ฒฝ ๋ฐ ์ฌ์ฉํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
Development Environment
Android Studio (version : Arctic Fox | 2020.3.1 Patch 4)
Device
Galaxy - S20 5G (OS : Android 10 / API Level : 29)
Using Library
- CountDownTimer
- SoundPool
๐ Coding Convention
Coding Convention
Kotlin ๊ณต์ ์ฝ๋ฉ ์ปจ๋ฒค์
ํด๋๊ตฌ์กฐ
๋ค์ด๋ฐ
- Class : Pascal Case
- Variable ๋ฐ Method : Camel Case
- Resource File : Lower Snake Case
โ๏ธ Commit Messge Rules
- feat : ๊ธฐ๋ฅ (์๋ก์ด ๊ธฐ๋ฅ)
- design : UI ๋์์ธ
๐ ๊ตฌํ ๊ธฐ๋ฅ
1. 1 ~ 60๋ถ ๋ฒ์์ ํ์ด๋จธ ์ค์
<!-- Gon : ํ ์๊ฐ์ ์ต๋ ๊ฐ์ผ๋ก ํ ๊ฒ์ด๋ฏ๋ก SeekBar max๊ฐ์ 60์ผ๋ก ์ค์ -->
<SeekBar
android:id="@+id/controlMinutesSeekBar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_marginEnd="15dp"
android:max="60"
android:progressTint="@color/gray"
android:thumb="@drawable/ic_thumb"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/remainMinutesTextView" />
controlMinutesSeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener{
// Gon : ๋๋๊ทธ ์ค ํธ์ถ
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
// Gon : ํ์ด๋จธ๋ฅผ 1์๊ฐ ๊ธฐ์ค์ผ๋ก ํํํ๊ธฐ ์ํด (progress * 1๋ถ) ์ธ์๋ก ๋๊น
if (fromUser)
updateRemainTime(progress * 60 * 1000L)
}
// Gon : ์ต์ด์ ํญํ์ฌ ๋๋๊ทธ ์์ ์ ํธ์ถ
override fun onStartTrackingTouch(seekBar: SeekBar?) {
stopCountDown()
}
// Gon : ๋๋๊ทธ๋ฅผ ๋ฉ์ถ๋ ๊ฒฝ์ฐ ํธ์ถ
override fun onStopTrackingTouch(seekBar: SeekBar?) {
if (seekBar == null) return
if (seekBar.progress == 0) stopCountDown()
else startCountDown(seekBar)
}
})
private fun updateRemainTime(millis: Long) = with(binding) {
val remainSeconds = millis / 1000
// Gon : ๋จ์ ์ ์ฒด ์๊ฐ์ ์ด๋ก ๋ฐ๊ธฐ ๋๋ฌธ์ ๋ถ,์ด๋ฅผ ํํํ๊ธฐ ์ํด์ 60(1๋ถ)์ผ๋ก ๊ฐ ์ฒ๋ฆฌ
remainMinutesTextView.text = resources.getString(R.string.minutesFormat, remainSeconds / 60)
remainSecondsTextView.text = resources.getString(R.string.secondsFormat, remainSeconds % 60)
}
2. 1์ด ๋ง๋ค ํ๋ฉด ๊ฐฑ์
// Gon : CountDown ์์
private fun startCountDown(seekBar: SeekBar) {
currentCountDownTimer = createCountDownTimer(seekBar.progress * 60 * 1000L).start()
currentCountDownTimer?.start()
//Gon : loop forever -> -1
tickingSoundId?.let {
soundPoll.play(it, 1F, 1F, 0, -1, 1F)
}
}
// Gon : ์ข
๋ฃ๊น์ง ๋จ์ ๋ฐ๋ฆฌ์ด, onTick() ๋ฉ์๋ ํธ์ถ ๊ฐ๊ฒฉ
private fun createCountDownTimer(millis: Long): CountDownTimer =
object: CountDownTimer(millis, 1000L) {
override fun onTick(millisUntilFinished: Long) {
updateRemainTime(millisUntilFinished)
updateSeekBar(millisUntilFinished)
}
override fun onFinish() {
completeCountDown()
}
}
private fun updateRemainTime(millis: Long) = with(binding) {
val remainSeconds = millis / 1000
// Gon : ๋จ์ ์ ์ฒด ์๊ฐ์ ์ด๋ก ๋ฐ๊ธฐ ๋๋ฌธ์ ๋ถ,์ด๋ฅผ ํํํ๊ธฐ ์ํด์ 60(1๋ถ)์ผ๋ก ๊ฐ ์ฒ๋ฆฌ
remainMinutesTextView.text = resources.getString(R.string.minutesFormat, remainSeconds / 60)
remainSecondsTextView.text = resources.getString(R.string.secondsFormat, remainSeconds % 60)
}
private fun updateSeekBar(millis: Long) = with(binding) {
// Gon : ๋จ์ ์ ์ฒด ๋ฐ๋ฆฌ์ด๋ก ๋ฐ๊ธฐ ๋๋ฌธ์ ๋ถ ๋จ์์ SeekBar๋ฅผ ํํํ๊ธฐ ์ํด์ 60000(1๋ถ)์ผ๋ก ๊ฐ ์ฒ๋ฆฌ
controlMinutesSeekBar.progress = (millis / 60000).toInt()
}
3. ํ์ด๋จธ ํจ๊ณผ์ ๋ฐ ์ข ๋ฃ ์๋ฆผ์
private fun init() = with(binding) {
... ์๋ต ...
tickingSoundId = soundPoll.load(this@MainActivity, R.raw.timer_tick, 1)
bellSoundId = soundPoll.load(this@MainActivity, R.raw.timer_bell, 1)
}
private fun startCountDown(seekBar: SeekBar) {
currentCountDownTimer = createCountDownTimer(seekBar.progress * 60 * 1000L).start()
currentCountDownTimer?.start()
//Gon : loop forever -> -1
tickingSoundId?.let {
soundPoll.play(it, 1F, 1F, 0, -1, 1F)
}
}
private fun completeCountDown() {
updateRemainTime(0)
updateSeekBar(0)
bellSoundId?.let {
soundPoll.play(it, 1F, 1F, 0, 0, 1F)
}
}