본문 바로가기

Android Project

[Android/Kotlin] FLO앱 클론코딩(2)

이번시간에는 재생버튼을 눌렀을때 1초마다 시간과 progressbar가 갱신되고, 정지버튼을 누르면 멈출수있도록 만들어보겠다

 

 

# Seekbar적용

activity_song으로 가서 progressbar를 SeekBar로 적용해준다

SeekBar는 사용자가 값을 선택할수있는 슬라이더 형태의 바를 말한다

<SeekBar
    android:id="@+id/song_progress_sb"
    android:layout_width="match_parent"
    android:layout_height="10dp"
    android:layout_marginTop="5dp"
    android:layout_marginStart="20dp"
    android:layout_marginEnd="20dp"
    android:layout_marginBottom="20dp"
    android:background="@null"
    android:paddingStart="0dp"
    android:paddingEnd="0dp"
    android:progress="0"
    android:max="100000"
    android:progressBackgroundTint="@color/gray_color"
    android:progressTint="@color/select_color"
    android:thumb="@color/transparent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/song_like_iv_layout"/>

 

progress는 SeekBar 의 현재진행상태를 나타내는데 0으로 초기화해주었다

progressBackgroundTint, progressTint를 사용해서 SeekBar의 기본색상과 진행상태일때의 색상을 설정해준다

thumb는 transparent로 설정해서 표시되지 않도록해준다

max는 100,000으로 설정해서 가독성이 떨어지지 않게해준다 (기본 설정값은 100이다)

 

 

 

# Song 데이터 클래스 수정

//데이터 클래스
data class Song(
    val title : String = "",         //노래제목
    val singer : String = "",        //가수
    var second : Int = 0,            //현재 재생시간
    var playTime : Int = 60,          //총 재생시간
    var isPlaying : Boolean = false     //노래가 현재 재생되고 있는지

)

 

Song 데이터 클래스를 위와같이 수정해준다

현재 재생시간, 총재생시간,재생중인지에 해당하는 데이터를 추가했다

총 재생시간은 60초라고 가정했다

 

 

# MainActivity

// text값들을 string값으로 바꿔서 가져오기 (title,singer,second,playTime,isPlaying에 해당하는 값들)
val song = Song(binding.mainMiniplayerTitleTv.text.toString(), binding.mainMiniplayerSingerTv.text.toString(),0,60,false)


//binding을 사용해서 id값 가져오기
//mainPlayerCl눌렀을때 SongActivity로 이동
binding.mainPlayerCl.setOnClickListener {
    //startActivity(Intent(this, SongActivity::class.java))
    val intent = Intent(this, SongActivity::class.java)

    //putExtra를 사용해서 데이터값들을 보내줌
    intent.putExtra("title", song.title)    //putExtra를 사용해서 title(제목)데이터를 보내줌
    intent.putExtra("singer", song.singer)  //putExtra를 사용해서 singer(가수)데이터를 보내줌 (보낸데이터는 SongActivity에서 받음)
    intent.putExtra("second", song.second)
    intent.putExtra("playTime", song.playTime)
    intent.putExtra("isPlaying", song.isPlaying)

    startActivity(intent)
}

 

MainActivity에 있는 내용을 수정해준다

putExtra를 사용해서  SongActivity로 데이터값들을 보내준다

 

 

 

# SongActivity에 initSong함수와 setPlayer함수 추가

//Song 데이터클래스를 초기화하기위한 함수
private fun initSong(){
    //MainActivity에서 보낸데이터 받음
    //만약 title값과 singer값이 넘어왔으면, getExtra를 사용해서 받아옴
    if(intent.hasExtra("title") && intent.hasExtra("singer")){
        song = Song(
            intent.getStringExtra("title")!!,
            intent.getStringExtra("singer")!!,
            intent.getIntExtra("second", 0),
            intent.getIntExtra("playTime",0),
            intent.getBooleanExtra("isPlaying", false)
        )
    }
  
}


// SongActivity화면에 받아와서 초기화된 Song에 대한 정보를 렌더링해주는 함수
private fun setPlayer(song : Song){
    //MainActivity에서 보낸데이터 받아서, 제목과 가수의 이름을 바꿔줌
    binding.songMusicTitleTv.text = intent.getStringExtra("title")!!
    binding.songSingerNameTv.text = intent.getStringExtra("singer")!!
    binding.songStartTimeTv.text = String.format("%02d:%02d", song.second / 60, song.second % 60)
    binding.songEndTimeTv.text = String.format("%02d:%02d", song.playTime / 60, song.playTime % 60)
    binding.songProgressSb.progress = (song.second * 1000 / song.playTime)

    setPlayerStatus(song.isPlaying)

}
//initSong,setPlayer 함수 데이터 받아오기
initSong()
setPlayer(song)

 

 

 

# Therad

시간을 세는작업을 무한루프로 구현할것이기 때문에 Timer를 위한 Thread를 생성해줘야한다

class SongActivity : AppCompatActivity(){


    lateinit var binding : ActivitySongBinding

    //Song 데이터클래스를 초기화하기위해 전역변수 선언
    lateinit var song: Song

    lateinit var timer : Timer
private fun startTimer(){
    // song의 총재생시간, 재생중 의 여부를 생성자의 입력으로 받음
    timer = Timer(song.playTime, song.isPlaying)
    timer.start()
}

//Thread를 사용해서 노래가 재생되면 seakbar와 텍스트의 값이 바뀌도록
//시간이 지남에따라 seakbar와 타이머 텍스트의 값을 바꿔줘야하기 때문에 inner class로 생성
//보여주고자 하는 노래가 총 몇초인지, 지금 노래가 재생중인지
inner class Timer(private val playTime : Int, var isPlaying: Boolean = true) : Thread(){
    //노래의 진행시간을 second와 mills로 나눠서 받아옴
    private var second : Int = 0
    private var mills : Float = 0F


    //해당 스레드가 실행할 코드작성 (run함수내에 작성된코드는 순차적으로 실행)
    override fun run() {
        super.run()

        // try-catch문에서 interrupt를 사용해서 thread를 멈추고, 오류가 발생했을때 특정명령을 실행하도록함
        try {
            //Timer는 계속 재생되어야하므로 when(true)를 사용해서 반복시켜줌 (무한루프)
            //second >= playTime일때 탈출하도록 설정하여, 노래의 총재생시간동안만 스레드가 실행되도록
            while(true) {
                if(second >= playTime) {
                    break
                }

                // 0.2초 대기
                while (!isPlaying) {
                    sleep(200)
                }

                // isPlaying이 true일때만(노래가 현재재생중일때만) ,50ms마다 프로그레스바를 슬라이드함 (즉, 초당 20번의 slide를 진행해서 자연스러운 슬라이딩효과줌)
                if(isPlaying) {
                    sleep(50)
                    mills += 50

                    //progress바
                    //mills는 ms단위이고, playTime은 초단위 -> playTime에 1000을 곱해야함(근데 여기서는 백분율이아닌 십만분율을 사용하고있기때문에 100곱함)
                    runOnUiThread{
                        binding.songProgressSb.progress = ((mills / playTime)*100).toInt()
                    }

                    //진행시간 타이머 (mills = 1000 = 1초)
                    //1초가 지날때마다 second(현재재생시간)을 1씩 증가시키고, songStartTimeTv의 텍스트 갱신
                    if (mills % 1000 == 0F){
                        runOnUiThread {
                            binding.songStartTimeTv.text = String.format("%02d:%02d", second / 60, second % 60)
                        }
                        second ++
                    }

                }
            }

        }catch (e: InterruptedException){
            Log.d("SongActivity", "쓰레드가 죽었습니다 ${e.message}")
        }

    }
}
//activity파괴될때 호출되는 onDestroy함수
override fun onDestroy() {
    super.onDestroy()
    //thread 종료
    timer.interrupt()
}

SongActivity.kt

 

마지막으로 Activity가 파괴될때 호출되는 onDestroy함수를 추가한다

 

 

 

 

 

최종적으로 이렇게 작동하는것을 볼수있다