코틀린 안드로이드 - 뽀모도로 타이머2(count down timer, 타이머기능 구현)

Jamwon·2021년 7월 7일
0

Kotlin_Android

목록 보기
19/30
post-thumbnail

저번에는 기본 UI를 만들었고
이번에는 count down timer를 만든다!

CountDownTimer

CountDownTimer 공식문서

현재 하고있는 졸프도 그렇고 토이프로젝트도그렇고 타이머를 쓸거같아서 잘알아둬야겠다!

공식문서의 카운트다운 타이머 30초와 1초를 인자로 전달받고
onTick()에서 1초마다 몇초가 남었는지 보여주고

onFinish()에서 30초가 지나면 끝났다고 text를 정해주는 코드이다.

단위는 밀리세컨드

타이머 기능만들기

MainActivity.kt

updateRemainTime()

    @SuppressLint("SetTextI18n")
    private fun updateRemainTime(remainMillis: Long) {
        val remainSeconds = remainMillis / 1000

        remainMinutesTextView.text = "%02d".format(remainSeconds / 60)
        remainSecondsTextView.text = "%02d".format(remainSeconds % 60)
    }

인자로는 남은 시간을 밀리세컨단위로 받은다음에 remainMiutesTextView와 remainSecondsTextView를 단위를 맞추어서 설정해준다.

updateSeekBar()

    private fun updateSeekBar(remainMillis: Long){
        seekBar.progress =(remainMillis/1000/60).toInt() //분으로 변환
    }

남은 시간을 Long으로 인자로 받아서 seekbar의 progress를 지정해준다.

createCountDownTimer()

    private fun createCountDownTimer(initialMillis: Long): CountDownTimer =
        object : CountDownTimer(initialMillis, 1000L) {
            override fun onTick(millisUntilFinished: Long) {
                updateRemainTime(millisUntilFinished)
                updateSeekBar(millisUntilFinished)
            }

            override fun onFinish() {
                updateRemainTime(0)
                updateSeekBar(0)
            }
        }

우선 createCountDownTimer함수를 만든다.
인자로는 카운트다운타이머의 시작 시간을 밀리세컨의 단위로 받고
CountDownTimer를 return 해준다.

kotlin은 return문을 쓰지 않더라도 위처럼 return 할수 있다.
CountDownTimer는 abstract class이기때문에

onTick() 과 onFinsh()를 구현해 줘야 오류가 뜨지 않는다.

onTick() - updateRemainTime(millisUntilFinished) updateSeekBar(millisUntilFinished)를 해주어서 1분마다 남은시간과 seekbar를 업데이트 해준다.

onFinish() - 끝났기 때문에 remainTime과 seekbar의 progress를 모두 0으로 지정해준다.

타이머와 seekbar 연동시키기

                override fun onStopTrackingTouch(seekBar: SeekBar?) {
                    seekBar ?:return //seekBar가 null일경우에 끝낸다 //앨비스 오퍼레이터
                    createCountDownTimer(seekBar.progress*60*1000L) //밀리세컨드로 변환
                }

seekbar를 터치로 조작하다가 손을 때면 타이머가 조작하도록 해야한다!!!

따라서 bindViews에있는 onStopTrackingTouch에서 구현을 해준다

seekBar ?: return
?: -> 엘비스 오퍼레이터이다. 좌측에있는 값이 null일때 우측에 있는 값을 return 한다!!!

바로 return 문을 전달하면 onStopTrackingTouch를 return 하게 된다.

😥 하지만 여기까지만 하면 seekbar를 설정하고 타이머가 시작되면 seekbar를 다시 설정해도 timer가 새로시작 하지 않는다!!!

따라서 private var currentCountDownTimer: CountDownTimer?=null
현재의 타이머 변수를 null로 초기화해서 지정하고 seekbar에서 시간 지정이 끝나면 타이머를 선언해주고 시작시킨다.

그리고 seekbar를 조절하기 시작하면 타이머를 cancel()시키고 null로 현재타이머 변수를 초기화시켜준다.

    private fun bindViews() {
        seekBar.setOnSeekBarChangeListener(
            object : SeekBar.OnSeekBarChangeListener {

                override fun onProgressChanged(
                    seekBar: SeekBar?,
                    progress: Int,
                    fromUser: Boolean
                ) {
                    if (fromUser) {
                        updateRemainTime(progress * 60 * 1000L)
                    }
                }

                override fun onStartTrackingTouch(seekBar: SeekBar?) {
                    currentCountDownTimer?.cancel()
                    currentCountDownTimer = null
                }

                override fun onStopTrackingTouch(seekBar: SeekBar?) {
                    seekBar ?: return //seekBar가 null일경우에 끝낸다 //앨비스 오퍼레이터


                    currentCountDownTimer =
                        createCountDownTimer(seekBar.progress * 60 * 1000L) //밀리세컨드로 변환
                    currentCountDownTimer?.start()
                }
            }
        )
    }

그리고 다시 seekbar를 지정했을때 저번에는 분만 지정했기 때문에 만들어둔 updateRemainTime을 이용해서 seekbar의 시간을 지정해준다.
이때 사용자가 지정했을떄가 업데이트 해야하기때문에 if(fromUser)문을 넣어준다.

MainActivity 전체 코드

class MainActivity : AppCompatActivity() {

    private val remainMinutesTextView: TextView by lazy {
        findViewById(R.id.txt_remainMinutes)
    }
    private val seekBar: SeekBar by lazy {
        findViewById(R.id.seekBar)
    }
    private val remainSecondsTextView: TextView by lazy {
        findViewById(R.id.txt_remainSeconds)
    }
    private var currentCountDownTimer: CountDownTimer? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        bindViews()
    }

    private fun bindViews() {
        seekBar.setOnSeekBarChangeListener(
            object : SeekBar.OnSeekBarChangeListener {

                override fun onProgressChanged(
                    seekBar: SeekBar?,
                    progress: Int,
                    fromUser: Boolean
                ) {
                    if (fromUser) {
                        updateRemainTime(progress * 60 * 1000L)
                    }
                }

                override fun onStartTrackingTouch(seekBar: SeekBar?) {
                    currentCountDownTimer?.cancel()
                    currentCountDownTimer = null
                }

                override fun onStopTrackingTouch(seekBar: SeekBar?) {
                    seekBar ?: return //seekBar가 null일경우에 끝낸다 //앨비스 오퍼레이터


                    currentCountDownTimer =
                        createCountDownTimer(seekBar.progress * 60 * 1000L) //밀리세컨드로 변환
                    currentCountDownTimer?.start()
                }
            }
        )
    }

    private fun createCountDownTimer(initialMillis: Long): CountDownTimer =
        object : CountDownTimer(initialMillis, 1000L) {
            override fun onTick(millisUntilFinished: Long) {
                updateRemainTime(millisUntilFinished)
                updateSeekBar(millisUntilFinished)
            }

            override fun onFinish() {
                updateRemainTime(0)
                updateSeekBar(0)
            }
        }

    @SuppressLint("SetTextI18n")
    private fun updateRemainTime(remainMillis: Long) {
        val remainSeconds = remainMillis / 1000

        remainMinutesTextView.text = "%02d".format(remainSeconds / 60)
        remainSecondsTextView.text = "%02d".format(remainSeconds % 60)
    }

    private fun updateSeekBar(remainMillis: Long) {
        seekBar.progress = (remainMillis / 1000 / 60).toInt() //분으로 변환
    }

결과 화면

seekbar로 타이머 설정 매주 잘된다!!!

새로 배운것

엘비스 ?: 연산자

엘비스 연산자는 널값을 허용하지 않는 변수에 널 값이 들어 갔을때 널 값을 변환할 수 있는 함수의 결과를 만들어 준다!.

위에서 쓰인것 처럼 val ?: return
함수 안에서 변수가 null일때 함수를 끝낼수 있다. 활용법은 다양할듯!

CountDownTimer(시작초, countDownInterval)

인자로는 시작하는 시간과 진행되는 인터벌을 인자로 받는다.
abstract 클래스이기 때문에
OnTick과 onFinish 함수를 꼭 구현해주어야된다.

onTick은 인자로 받은 인터벌 마다 실행되고 onFinish는 타이머가 끝났을때 실행되는 함수이다.

엄청나게 새로운걸 많이배웠다기 보다는 seekbar와 countdownTimer간의 연동 그리고 textview update 이런것들이 얽혀있어서 차근차근 따라가야할 것 같다. 다시 해보고 내껏으로 만들기!!

다음 챕터에서는 소리를 입힌다!

profile
한걸음씩 위로 자유롭게

0개의 댓글