코틀린 안드로이드 - 녹음기(기본 UI구성,권한요청)

Jamwon·2021년 7월 20일
0

Kotlin_Android

목록 보기
21/30

이번에는 녹음기 어플을 만든다!
녹음을 하면 소리의 스팩트럼이 나오면서 녹음이되고 재생을 할수 있다

1.마이크를 통해 음성 녹음
2.녹음한 음성 재생
3.음성을 시각화해서 본다 를 만들기 위해

Request runtime permissions
CustomView
MediaRecorder 를 이용한다!

녹음 state

녹음전 -> 녹음 중 -> 녹음 후 -> 재생 중

이 4개의 상태가있는데 여기서
녹음후 -> 녹음전
재생중 -> 녹음후 , 녹음전
이 3상태가 더 있다.

enum class State {
    BEFORE_RECORDING,
    ON_RECORDING,
    AFTER_RECORDING,
    ON_PLAYING
}

State라는 enum class를 생성해줘서 미리 state들을 만들어놓는다.

RecordButton

state에따라 icon이 바뀌는 button을 만들기위해 customView를 만든다.

AppCompat

AppCompat은 라이브러리인데 안드로이드가 매년 업데이트 하기때문에 이전 버전에서 업데이트된 UI를 지원하지 않을 경우 앱 내부의 AppCompat라이브러리에 포함된 UI를 사용한다.

"공식문서에서는 이전 API버전의 플랫폼에서 새 API에 엑세스 할 수 있습니다" 라고 쓰여있다.

우선 vector assest을 이용해서 icon을 만들자

빨간색의 녹음버튼 생성!

정지버튼 생성!

재생버튼 생성!

RecordButton.kt

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_PLAYING -> {
                    setImageResource(R.drawable.ic_stop)
                }
            }
        }
}

view 클래스를 만들때 context와 AttributeSet인자로 받고 원하는 return 값을 지정하면된다. 여기서는 State마다 바뀌는 ImageButton이기 때문에 ImageButton으로 !

이 생성자를 JVMOverLoad를 이용하면 쉽게 정의할 수 있게 해준다는데 강의에서는 사용하지는 않았다. 다음에 써보자!

State에서 선언해주었던 상태들마다 imageButton의 image를 setImageResource를 이용해서 선언한다.!!!

activity_main.xml

이제 xml에 만들어둔 ImageButton을 적용시켜보자!

<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">

    <Button
        android:id="@+id/resetButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="RESET"
        app:layout_constraintTop_toTopOf="@id/recordButton"
        app:layout_constraintBottom_toBottomOf="@id/recordButton"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@id/recordButton"
        tools:ignore="HardcodedText" />

    <TextView
        android:id="@+id/txt_recordTime"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="20dp"
        android:text="00:00"
        app:layout_constraintBottom_toTopOf="@id/recordButton"
        app:layout_constraintLeft_toLeftOf="@id/recordButton"
        app:layout_constraintRight_toRightOf="@id/recordButton"
        tools:ignore="HardcodedText" />

    <com.example.voicerecorder.RecordButton
        android:id="@+id/recordButton"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_marginBottom="50dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

아까 customeView로 만들어놓았던 RecordButton과 시간 그리고 resetButton을 구현해준다.

record버튼상태 추가 /권한설정

MainActivity.kt

class MainActivity : AppCompatActivity() {

    private val recordButton: RecordButton by lazy {
        findViewById(R.id.recordButton)
    }
    //필요한 권한 선언
    private val requiredPermissions = arrayOf(Manifest.permission.RECORD_AUDIO)
    //초기 state 설정
    private var state = State.BEFORE_RECORDING

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

        requestAudioPermission()
        initViews()
    }

    private fun requestAudioPermission(){
        requestPermissions(requiredPermissions, REQUEST_RECORD_AUDIO_PERMISSION)

    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)

        //권한이 부여받은게 맞는지 check 권한부여받았으면 true 아니면 false
        val audioRequestPermissionGranted =
            requestCode == REQUEST_RECORD_AUDIO_PERMISSION &&
                grantResults.firstOrNull() ==PackageManager.PERMISSION_GRANTED

        //권한이 부여되지않으면 어플 종료
        if(!audioRequestPermissionGranted){
            finish()
        }
    }

    private fun initViews() {
        recordButton.updateIconWithState(state)
    }
    
    companion object{
        //permission code 선언
        private const val REQUEST_RECORD_AUDIO_PERMISSION =201
    }
}

초기 state를 State enum class에서 불러와서 지정해주고 recordButton도 선언해준다.

그리고 initViews() 함수를 만들어서

updateIconWithState()

RecordButton에서 만든 함수를 이용해서 처음 icon을 지정해준다.

화면이 이렇게 잘나온다!

녹음 권한 설정

MediaRecorder 공식문서

녹음을 하기위해서는 사용자에게 마이크의 접근한다는 것을 알려줘야한다.

녹음 기능은 안드로이드에서 dangerous permission으로 취급하기 때문에 앱 runtime에 꼭 권한을 허용할것인지 사용자에게 물어봐야한다!

dangerous permission 또는 Runtime permission 이라고 불린다.

카메라나 마이크처럼 사용자의 사생활에 영향을 줄수있는 기능들이다.


안드로이드에서 권장하는 워크플로우

이런식으로 있다 정도만 알아두자!

<uses-permission android:name="android.permission.RECORD_AUDIO" />

Manifest에 권한선언을 해준다.

requestAudioPermission()

requestPermission() 함수를 사용 인자는 어떤 permission을 요청할것인지 , 그리고 어떤 request의 결과를 받을건지 requestcode를 지정해줘야한다.

전역변수로 REQUEST_RECORD_AUDIO_PERMISSION을 특정한 값으로 선언해준다 (여기선 201)

그리고 필요한 권한(여기선 RECORD_AUDIO권한)을 배열에 넣어서 선언해주고

requestPermission()의 인자로 넣어준다.

onRequestPermissionResult()

override 함수로 requestPermission의 결과값을 받아온다.

requestCode - requestAudioPermissions에서 쓰인 requestCode
grantResults - 배열로 입력한 권한들에 대해서 권한이 부여됬는지 안되었는지는 표현하는 배열값이다.

권한설정의 결과값으로 requestCode와 REQUEST_RECORD_AUDIO_PERMISSION이 같고 (사실 이경우엔 요구권한이 1개라 크게 중요하진 안을거 같다)

grantResults.firstOrNull - 요구 권한이 한개 이기 때문에 배열의 첫번째 원소만 비교한다.

비교해서 PERMISSION_GRANTED면 true 이고 아니면 false이다.

그리고 이 결과값이 false일때 앱을 종료시키는 if문을 넣어준다.

이렇게 되면 어플이 시작되었을때 권한을 물어보고 거부를 하면 어플꺼지고 권한을 부여하면 메인화면으로 이동한다!

새로 배운것

enum class

enum 은 enumerated type 즉 서로 연관된 값을의 집합이라는 뜻이다.

Enum을 쓰는 이유?

인스턴스를 생헝하지 않아도 되기때문에 값에 대한 안정성이 보장된다.

코드가 단순해지고 가독성이 좋아진다.

Custom View

여기저기서 많이 보이는 기능 사용자가 원하는 View를 만들때 사용한다.

context와 AttributeSet인자로 받고 원하는 요소를 return 해주는 class로 만들면 된다.

위에서는 ImageButton을 만들었는데
setImageResource() 함수를 이용해서 특정 State마다 icon이 바뀌도록 만들어주었다.

이를 xml에 이식시켜서 View에 띄어줘었다.

Runtime permission

권한중에 민감한 권한은 꼭 앱이 돌아가고있을때 권한을 사용자에게 물어봐야 한다.

requestPermission() 함수를이용해서
필요한 권한의 배열/ requestcode 를 인자로 넣어서 권한요청을 한다.

onRequestPermissionResult()

requestPermission()의 result값을 받아온다.
requestcode와 permissions , grantResults를 받아온다. 이를 이용해서 권한이 주어지지 않았을때 혹은 주어졌을대를 나누어서 작업을 할 수있다.

다음에 이어서~!!

화이팅!!!

profile
한걸음씩 위로 자유롭게

0개의 댓글