- Request Runtime Permissions
- CustomView
- MediaRecorder
- ๋ง์ดํฌ๋ฅผ ํตํ ์์ฑ ๋ น์
- ๋ น์ํ ์์ฑ ์ฌ์
- ๋ น์ ์ค์ธ ์์ฑ ์๊ฐํ
๊ฐ๋ฐ ๊ณผ์ (๋ ธ์ ์์ ํ์ธ)
package com.example.record
enum class State {
BEFORE_RECORDING,
ON_RECORDING,
AFTER_RECORDING,
ON_PALYING
}
์ํ์ ๋ฐ๋ผ ๋ค๋ฅธ UI๋ฅผ ๋ณด์ฌ์ค์ผํ๋๋ฐ ์ด๋ฅผ Enum
์ผ๋ก ๋ฏธ๋ฆฌ ์ ์ํด์คฌ๋ค.
๋ฒํผ์ ์ํ๊ด๋ฆฌ๋ฅผ ๋ณด๋ค ํธํ๊ฒ ํ๊ธฐ ์ํด์ ImageButton
์ ์์ํ๋ RecordButton
ํด๋์ค๋ฅผ ์ ์ํด์คฌ๋ค.
package com.example.record
import android.content.Context
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatImageButton
class RecordButton(
context: Context,
attrs: AttributeSet
) : AppCompatImageButton(context, attrs) {
fun updateIconWithState(state: State) {
when (state) {
State.BEFORE_RECORDING ->
setImageResource(R.drawable.ic_record)
State.ON_RECORDING ->
setImageResource(R.drawable.ic_stop)
State.AFTER_RECORDING ->
setImageResource(R.drawable.ic_play)
State.ON_PALYING ->
setImageResource(R.drawable.ic_stop)
}
}
}
์ง์ ๋ง๋ค์ด์ค UI์ด๋ฏ๋ก ํ์ ๋ฒ์ ผ์ ์๋๋ก์ด๋ API์์๋ ์ ์ฉ์ด ์๋ ์ ์๋ค. ์ด๋ฅผ ์ํด AppCompat ํค์๋๋ฅผ ์ฌ์ฉํด์ผํ๋ค๊ณ ์๋ ค์ค๋ค. ๋ํ Record ํด๋์ค
๋ด๋ถ์ ์ํ์ ๋ฐ๋ผ ๋ฒํผ์ ๋ชจ์์ ๋ณ๊ฒฝํด์ฃผ๋ updateIconWithState
๋ฉ์๋๋ฅผ ์ ์ํด์คฌ๋ค.
๋ฐ๋ก ์ ์ํด์ค RecordButton ํด๋์ค
๋ฅผ ํตํด ๋ฒํผ์ ๊ตฌํํ๋ค. ์ด๊ธฐ ์ํ๋ฅผ ์ง์ ํด์ฃผ์ง ์์๊ธฐ์ ์ฒซ ๋ฒ์งธ ์ฌ์ง์ ๋ณด๋ฉด ๋น์นธ์ผ๋ก ๋จ๋ ๊ฒ์ ๋ณผ ์ ์๋ค. ์ด๊ธฐ ์ํ๋ฅผ ์ง์ ํด์ฃผ๊ธฐ ์ํด MainActivity
์์ RecordButton
๊ณผ ์ฐ๊ฒฐ๋ ๋ณ์๋ฅผ ์ ์ธํ๊ณ ํด๋น ํด๋์ค์์ ์ ์ํด์ค updateIconWIthState
๋ฉ์๋๋ฅผ ์ฌ์ฉํด์ ํ์ฌ ์ํ๋ฅผ ์
๋ฐ์ดํธ ํด์คฌ๋ค. ๋ ๋ฒ์งธ ์ฌ์ง์์๋ ic_record
์ Vector Asset์ผ๋ก ๋์ด ์๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
package com.example.record
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class MainActivity : AppCompatActivity() {
private val recordButton : RecordButton by lazy {
findViewById(R.id.recordButton)
}
private var state = State.BEFORE_RECORDING
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initViews()
}
fun initViews() {
recordButton.updateIconWithState(state)
}
}
์ง๋๋ฒ์ ๊ฐค๋ฌ๋ฆฌ ์ฑ์ ์ ์ํ ๋์ฒ๋ผ ์ด๋ฒ์๋ ์ฌ์ฉ์์ ๋ง์ดํฌ์ ์ ๊ทผํด์ผ ํ๋ฏ๋ก ๊ถํ
์ ์์ฒญํด์ผํ๋ค. AndroidManifest.xml
์์ ์ฐ์ ๊ถํ์ ์์ฒญํ๋ ์ฝ๋๋ฅผ ์์ฑํ๋ค.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="<http://schemas.android.com/apk/res/android>"
package="com.example.record">
<!--๊ถํ ์์ฒญ-->
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Record">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initViews()
requestAudioPermission()
}
์ด๋ฒ ๋
น์๊ธฐ ์ฑ์ ์ฑ์ ์คํํ๋ฉด ๋์์ ๊ถํ์ ์์ฒญํ๋๋ก ํ๋ค. ๋ฐ๋ผ์ onCreate
๋ฉ์๋์ ๊ถํ์ ์์ฒญํ๋ ๋ฉ์๋๋ฅผ ๊ตฌํํ๋ ค๊ณ ํ๋ค.
private val requiredPermissions = arrayOf(Manifest.permission.RECORD_AUDIO) (์ฒซ ๋ฒ์งธ ์ธ์)
private fun requestAudioPermission() {
requestPermissions(requiredPermissions, REQUEST_RECORD_AUDIO_PERMISSION)
}
// ... ์ค๋ต
// ์ ์ ๋ณ์๋ก ๋ง๋ค์ด์ฃผ๊ธฐ ์ํด companion ๊ฐ์ฒด ์ฌ์ฉ (๋๋ฒ์งธ ์ธ์)
companion object {
private const val REQUEST_RECORD_AUDIO_PERMISSION = 201
}
requestPermissons
๋ฉ์๋๋ ์ฒซ ๋ฒ์งธ ์ธ์๋ก ๊ถํ์ ๋ถ์ฌํ ๋ชฉ๋ก(๋ฐฐ์ด)์ ๋ ๋ฒ์งธ ์ธ์๋ก ์๋ต์ฝ๋๋ฅผ ๋ฐ๋๋ค. ๋ชฉ๋ก์ ์๋ ๊ถํ๋ค์ ์ฌ์ฉ์์๊ฒ ์์ฒญํ๋ค. ๊ถํ์ด ์๋ฝ๋๋ฉด ๋ ๋ฒ์งธ ์ธ์๋ก ๋ฃ์ด์ค ๊ฐ์ ์๋ต์ฝ๋๋ก ๊ฐ์ง๋ค.
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
val audioRecordPermissionGranted =
requestCode == REQUEST_RECORD_AUDIO_PERMISSION && grantResults.firstOrNull() == PackageManager.PERMISSION_GRANTED
if (!audioRecordPermissionGranted) {
finish()
}
}
๋ํ ์ฌ์ฉ์๊ฐ ๊ถํ์ ๊ฑฐ๋ถํ ๊ฒฝ์ฐ ์ฑ์ ์ข
๋ฃ์ํค๊ธฐ ์ํด onRequestPermissionsResult
๋ฅผ ์ค๋ฒ๋ผ์ด๋ฉ ํด์คฌ๋ค. ๊ถํ์ ๋ถ์ฌ ๋ฐ์ ๊ฒฝ์ฐ ์๋ต ์ฝ๋์๋ ์ ์ ๋ณ์
๋ก ์ ์ธํด์ค REQUEST_RECORD_AUDIO_PERMISSION
๊ฐ ์ ์ฅ๋์ด ์๋ค. ๋ํ ์คํ ๊ฒฐ๊ณผ๊ฐ grantResult
์ ๋ฐฐ์ด๋ก ์ ์ฅ๋์ด ์๋๋ฐ ํ์ฌ ์ค๋์ค์ ๋ํ ๊ถํ
๋ง ์์ฒญํ์ผ๋ฏ๋ก 1๊ฐ๋ง ๋๋ ค๋ฐ๋๋ค. ๋ฐ๋ผ์ firstOfNull
๋ฉ์๋๋ฅผ ํตํด ์คํ ๊ฒฐ๊ณผ๋ฅผ ๋น๊ตํ๋ค.
๋
น์ ๊ธฐ๋ฅ์ ๊ตฌํํ๊ธฐ ์ํด์๋ MediaRecorder
๋ฅผ ์ฌ์ฉํ๋ค. ํ์ง๋ง ๋ฐ๋ก ์ฌ์ฉํ ์๋ ์๊ณ ์๋ ๊ทธ๋ฆผ๊ณผ ๊ฐ์ ์ํ๋
๋ฅผ ๊ฐ์ง๋ค.
์์๋ฅผ ๋์ดํด๋ณด์.
-
setAudioSource
๋ก ๋ง์ดํฌ์ ์ ๊ทผํ๋ค. -
setOutputFormat
์ผ๋ก ํฌ๋งท ๋ฐฉ์์ ์ง์ ํ๋ค. -
setAudioEncorder
๋ฅผ ํตํด ์ธ์ฝ๋ ๋ฐฉ์์ ์ง์ ํ๋ค.์ธ์ฝ๋ ๋ฐฉ์์ ์ง์ ํ๋ ์ด์ ๋ ๋ น์ ํ์ผ์ ํฌ๊ธฐ๋ฅผ ์ค์ด๊ธฐ ์ํจ์ด๋ค.
-
setOutputFile
์ ํตํด ํ์ผ์ด ์ ์ฅ๋ ๊ฒฝ๋ก๋ฅผ ์ง์ ํ๋ค. -
prepare
์ ํตํด ๋ชจ๋ ์ค๋น๋ฅผ ์๋ฃํ๋ค.
์ด ์์์ ๋ฐ๋ผ ์ฝ๋๋ฅผ ์์ฑํ๋ฉด ๋๋๋ฐ ์ฃผ์ํ ์ ์ด ์๋ค. ๋ฐ๋ก ๋ น์ ํ์ผ์ ํฌ๊ธฐ์ด๋ค. ๋ น์ ํ์ผ์ ๊ฒฝ์ฐ ์งง๊ฒ๋ ๋ช ์ด, ๊ธธ๊ฒ๋ ๋ช ์๊ฐ์ด ๋ ์๋ ์๋ค. ์ด๋ฐ ์ํฉ์ ๋ง์ฝ ์ฑ์ ๋ก์ปฌ ์คํ ๋ฆฌ์ง์ ์ ์ฅํ๊ฒ ๋๋ค๋ฉด ์ฑ ์ฉ๋์ด ์๋นํ ์ปค์ง ์ ์๋ค. ์ด๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ํด๋ํฐ์ ์บ์ ์คํ ๋ฆฌ์ง์ ์ ์ฅํ๊ณ ์ญ์ ํ๋ ๋ฐฉ์์ผ๋ก ์ฑ์ ์ ์ํ๋ ค๊ณ ํ๋ค.
์ค์ ๋ก ๊ณต์ ๋ฌธ์๋ฅผ ๋ณด๋ฉด ์ฑ์ ๋ก์ปฌ ์คํ ๋ฆฌ์ง๊ฐ ์ถฉ๋ถํ ๊ณต๊ฐ์ ์ ๊ณตํ์ง ์๋ ๋ค๋ฉด ์ธ๋ถ ์คํ ๋ฆฌ์ง๋ฅผ ์ฌ์ฉํ๋ผ๊ณ ๋์์๋ค.
private var recorder: MediaRecorder? = null
// ... ์ค๋ต
private fun startRecording() {
recorder = MediaRecorder().apply {
setAudioSource(MediaRecorder.AudioSource.MIC) // ๋ง์ดํฌ์ ์ ๊ทผ
setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP) // ํฌ๋ฉง ์ง์
setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB) // ์ธ์ฝ๋ ๋ฐฉ์ ์ง์
setOutputFile(recordingFilePath) // ์ง์ ํด์ค ๊ฒฝ๋ก์ ์ ์ฅ
prepare()
}
recorder?.start()
state = State.ON_RECORDING
}
์ฐ์ MediaRecorder
๊ฐ์ฒด๋ฅผ Nullable
๋ก ๋ง๋ค์ด์ฃผ๊ณ startRecording
๋ฉ์๋๋ฅผ ๋ง๋ค์ด์คฌ๋ค. ์์์ ๋์ดํ ์์์ ๋ฐ๋ผ Prepare ์ํ๊น์ง ์งํํ๊ณ ๋
น์์ ์์ํ๋ค. ์ดํ ์ํ๋ฅผ ON_RECORDING
์ผ๋ก ๋ฐ๊ฟ์คฌ๋ค.
private var state = State.BEFORE_RECORDING
set(value) {
field = value
recordButton.updateIconWithState(value)
}
์ํ๊ฐ ๋ณ๊ฒฝ๋๋ ๊ฒฝ์ฐ ์ค์ ํ๋ฉด์์์ ์์ด์ฝ๋ ๋ณ๊ฒฝ๋์ผํ๋ฏ๋ก setter
๋ฅผ ํตํด ๊ฐ์ ๋ณ๊ฒฝ์์ผ์คฌ๋ค.
๋ น์์ ์ค์งํ๋ ๋ฉ์๋์ด๋ค. ๊ฐ๋จํ๊ฒ ๊ตฌํํ๋ค.
private fun stopRecording() {
recorder?.run {
stop()
release() // ๋ฉ๋ชจ๋ฆฌ ํด์
}
recorder = null
state = State.AFTER_RECORDING
}
๋
น์์ด ์งํ๋๋ฉด recorder ๊ฐ์ฒด์๋ null์ด ์๋๊ฒ ๋๋ค. ๋ฐ๋ผ์ run์ ์คํํ๊ฒ ๋๊ณ ์ด๋ stop
์ ์คํํ๊ณ release
๋ฅผ ํตํด ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํด์ ํ๋ค. ์ดํ recorder ๊ฐ์ฒด๋ฅผ null๋ก ๋ง๋ค์ด์ฃผ๊ณ ์ํ๋ฅผ AFTER_RECORDING
์ผ๋ก ๋ฐ๊ฟ์คฌ๋ค.
๋
น์๋ ํ์ผ์ ๋ฃ๊ธฐ ์ํด์๋ MediaPlayer
๋ฅผ ์ฌ์ฉํ๋ค. ์ญ์ MediaRecorder
์ฒ๋ผ ์ํ ๊ฐ์ ๊ฐ๋๋ค. ๊ณต์ ๋ฌธ์์ ๋์์๋ ์ํ๋๋ฅผ ์ดํด๋ณด์.
MediaRecorder๋ณด๋ค Prepare๊น์ง์ ๊ณผ์ ์ด ์ข ๋ ๊ฐ๊ฒฐํ๋ค.
setDataSource
๋ก ํ์ผ์ ๋ถ๋ฌ์จ๋ค.prepare
์ ํตํด ๋ชจ๋ ์ค๋น๋ฅผ ์๋ฃํ๋ค.
๋ ๊ณผ์ ์ด ๋์ด๋ค. ์ค์ ์ฝ๋๋ฅผ ์ดํด๋ณด์.
private var player: MediaPlayer? = null
// ... ์ค๋ต
private fun startPlaying() {
player = MediaPlayer().apply {
setDataSource(recordingFilePath)
prepare()
}
player?.start()
state = State.ON_PALYING
}
player
๊ฐ์ฒด๊ฐ null์ด ์๋ ๊ฒฝ์ฐ start
๋ฅผ ์คํํ๋ค. ์ดํ ON_PLAYING
์ํ๋ก ๋ฐ๊ฟ์ค๋ค.
stopRecording
๊ณผ ์ ์ฌํ๋ค.
private fun stopPlaying() {
player?.release()
player = null
state = State.AFTER_RECORDING
}
์ด๋ ๊ฒ ๋ น์ ์์, ๋ น์ ์ค์ง, ๋ น์ ํ์ผ ์ฌ์, ๋ น์ ํ์ผ ์ฌ์ ์ค์ง ์ด 4๊ฐ์ง์ ์ํ์ ๋ฐ๋ฅธ ๋ฉ์๋๋ฅผ ๋ชจ๋ ๋ง๋ค์๊ณ ๋ น์ ๋ฒํผ๊ณผ ์ฐ๊ฒฐํด์ฃผ๋ฉด ๋๋ค.
setOnClickListener
๋ฅผ ํตํด ๋ฒํผ ํด๋ฆญ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ ํ์ฌ ๋ฒํผ์ ์ํ์ ๋ฐ๋ผ ํน์ ๋ฉ์๋๋ฅผ ์คํํ๋๋ก ๊ตฌํํ๋ค.
private fun bindViews() {
recordButton.setOnClickListener {
when (state) {
State.BEFORE_RECORDING -> startRecording()
State.ON_RECORDING -> stopRecording()
State.AFTER_RECORDING -> startPlaying()
State.ON_PALYING -> stopPlaying()
}
}
}
์ฐ์ View
๋ฅผ ์์ํ๋ SoundVisualizerView
ํด๋์ค๋ฅผ ๋ง๋ค์ด์คฌ๋ค. ์๋๋ก์ด๋์ onDraw
๋ฉ์๋๋ฅผ ์ค๋ฒ๋ผ์ด๋ฉํด์ ์ค๋์ค๋ฅผ ์๊ฐํํ๋ค. onDraw์ ๋งค๊ฐ๋ณ์๋ Canvas
ํด๋์ค์ด๋ค. Canvas ๊ฐ์ฒด๋ ๋ทฐ์์ ํ
์คํธ, ์ , ๋นํธ๋งต ๋ฑ ๋ค์ํ ๊ทธ๋ํฝ์ ๊ทธ๋ฆฌ๊ธฐ ์ํ ๋ฉ์๋๋ฅผ ์ ์ํ๋ค. ์ด ๋ถ๋ถ์ ์๋๋ก์ด๋ ๊ณต์ ๋ฌธ์์ ์ ๋์์๋ค. ํ์ง๋ง ๊ทธ๋ฆฌ๊ธฐ ๋ฉ์๋๋ฅผ ํธ์ถํ๊ธฐ ์ ์ Paint ๊ฐ์ฒด๋ฅผ ๋จผ์ ๋ง๋ค์ด์ผํ๋ค. Canvas ํด๋์ค๊ฐ ๊ทธ๋ฆฌ๋ ๋ด์ฉ์ ๋ํ ์ ๋ณด๋ฅผ ๋ด๊ณ ์๋ค๋ฉด, Paint ํด๋์ค๋ ๊ทธ๋ฆฌ๋ ๋ฐฉ๋ฒ์ ๋ํ ์ ๋ณด๋ฅผ ๋ด๊ณ ์๋ค. ์๋ฅผ ๋ค์ด Canvas์๋ ์ง์ฌ๊ฐํ์ ๊ทธ๋ฆฌ๋ ๋ฉ์๋๊ฐ ์๊ณ Paint์๋ ์ง์ฌ๊ฐํ์ ๋ฌด์จ ์์ผ๋ก ์ฑ์ฐ๋์ง์ ๋ํ ๋ฉ์๋๊ฐ ์๋ค.
companion object {
private const val LINE_WIDTH = 10F
private const val LINE_SPACE = 15F
private const val MAX_AMPLITUDE = Short.MAX_VALUE.toFloat()
private const val ACTION_INTERVAL = 20L
}
// ... ์ค๋ต
private val amplitudePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = context.getColor(R.color.purple_500)
strokeWidth = LINE_WIDTH
strokeCap = Paint.Cap.ROUND
}
์ฐ์ Paint
๊ฐ์ฒด๋ฅผ ๋ง๋ ๋ค. ANTI_ALIAS_FLAG
์์ฑ์๋ฅผ ํตํด ๊ฐ์ฒด๋ฅผ ๋ง๋ค๊ณ apply
๋ก ์์ฑ ๊ฐ์ ์ ํด์ค๋ค. ์ดํ์ onDraw
๋ฉ์๋๋ก View๋ฅผ ๋ง๋ค์ด์ฃผ๋ฉด ๋๋ค. ํ์ง๋ง ๊ทธ ์ ์ ์์์ผํ ๊ฒ์ด ์๋ค. View์ ํฌ๊ธฐ์ด๋ค.
private var drawingWidth: Int = 0
private var drawingHeight: Int = 0
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
drawingWidth = w
drawingHeight = h
}
onSizeChanged
๋ฅผ ์ค๋ฒ๋ผ์ด๋ฉํด์ View์ ํฌ๊ธฐ๋ฅผ ๊ฐ์ ธ์จ๋ค. ์ด์ ์ฐ๋ฆฌ๊ฐ ์ค๋์ค๋ฅผ ์๊ฐํํ ์์ญ์ ํฌ๊ธฐ๋ฅผ ์์๋๋ค.
private var drawingAmplitudes: List<Int> = emptyList()
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas ?: return
val centerY = drawingHeight / 2f
var offsetX = drawingWidth.toFloat()
drawingAmplitudes
.let { amplitudes ->
if(isReplaying) {
amplitudes.takeLast(replayingPosition)
}
else {
amplitudes
}
}
.forEach { amplitude ->
val lineLength = amplitude / MAX_AMPLITUDE * drawingHeight * 0.8F
offsetX -= LINE_SPACE
if (offsetX < 0) return@forEach
canvas.drawLine(
offsetX,
centerY - lineLength / 2F,
offsetX,
centerY + lineLength / 2F,
amplitudePaint
)
}
}
์ฐ์ ์ธ์ฝ๋ฉ๋ ์์ฑ์ ์ ์ฅํ ๋ฆฌ์คํธ๋ฅผ ์ ์ธํด์คฌ๋ค. ์ดํ ๋ ๊ฐ์ ๊ธฐ์ค์ ์ ๋ง๋ค์ด์คฌ๊ณ forEach
๋ฅผ ํตํด ์ ์ฅ๋ ์์์ ํ๋์ฉ ํ๋ฉด์ ํ์ํ๋ ๋ฐฉ์์ผ๋ก ์งํํ๋ค. ์ผ๋จ ์ขํ์ฃฝ ๊ฐ๋
๋ถํฐ ์ง๊ณ ๋์ด๊ฐ์.
์ด๋ ๊ฒ ์ขํ๊ฐ ์ค์ ๋๋๋ฐ ์ฝ๋์์ offsetX
๊ฐ View์ ๋๋น๋ก ์ง์ ๋์ด ์๋ ๊ฒ์ ๋ณผ ์ ์๋ค. ์ฆ, ๊ฐ์ฅ ์ฐ์ธก๋ถํฐ ๋ฆฌ์คํธ์ ์์๋ฅผ ์ถ๋ ฅํ๊ฒ ๋๋ค.
Runnable
์ธํฐํ์ด์ค๋ฅผ ์ด์ฉํด์ ๊ตฌํํ๋ค.
var onRequestCurrentAmplitude: (() -> Int)? = null
private val visualizeRepeatAction: Runnable = object : Runnable {
override fun run() {
if (!isReplaying) {
val currentAmplitude = onRequestCurrentAmplitude?.invoke() ?: 0
drawingAmplitudes = listOf(currentAmplitude) + drawingAmplitudes
} else {
replayingPosition++
}
invalidate()
handler?.postDelayed(this, ACTION_INTERVAL)
}
}
onRequestCurrentAmplitude
๋ฅผ ์ ์ํด์ ํจ์ ๊ฐ์ด ๋ฐํ๋๋๋ก ๊ตฌํํ๋๋ฐ ์ด๋ฅผ handler
๋ฅผ ํตํด 20milliseconds ๋ง๋ค ์คํ๋๋๋ก ํ๋ค. ์ฆ 20milliseconds ๋ง๋ค ํ์ฌ ์์ฑ์ maxAmplitude
๋ฅผ ๊ฐ์ ธ์จ๋ค. ์ดํ ๊ธฐ์กด์ ์์ฑ์ด ๋ด๊ฒจ์๋ ๋ฆฌ์คํธ์ ๋งจ ์์ ์๋ก์ด ์์ฑ์ ์ถ๊ฐํด์คฌ๋ค. ์ฌ์ ๋ฒํผ ์์ ์๋ ๋
น์ ์๊ฐ ์ถ๋ ฅ ์ญ์ ๋์ผํ๊ฒ Runnable
์ ์ฌ์ฉํด์ ๊ตฌํํ๋ค.