오늘의 공부는?!/Android

[Android] 녹음기 만들기 with 코틀린(4) - 오디오 시각화(Custom Drawing)

꼬물쥰 2022. 2. 25. 14:39

커스텀뷰를 정의해서 녹음된 음성을 시각화하였다. 음폭에 따라 drawLine을 이용해 그려준다.

커스텀 뷰에는 Canvas로 무엇을 그릴지 Paint로 어떻게 그릴지 설정해준다. ( custom drawing )

//Paint 생성(음성시각화)
private val amplitudePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
    color = context.getColor(R.color.purple_500)
    strokeWidth = LINE_WIDTH
    strokeCap = Paint.Cap.ROUND
}

paint객체를 정의해주고 아래에서 라인을 그려준다.

 

onSizeChanged를 오버라이드 해서 그릴 라인의 사이즈가 변경되는 경우 너비와 높이를 가져와 재설정할 수 있다. 

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
    super.onSizeChanged(w, h, oldw, oldh)
    drawingWidth = w
    drawingHeight = h
}

 

onDraw

실제 paint 객체를 사용하여 원하는 라인을 그린다. 진폭에 대한 배열을 이용해 하나씩 그려준다.

이때, 우측부터 그리기 위해서 우측 좌표를 가지고 있다가 하나씩 그리면서 LINE_SPACE만큼 빼주면서 그린다.

뷰영역에서 넘어가는 경우에는 return을 해서 더이상 그리지 않는다.

재생할 때는 뒤에서부터 보여줘야하기 때문에 takeLast를 사용하였다.

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
        )
    }
}

 

녹음을 시작하면 오디오 시각화를 실시간으로 하기위해 runnable 객체를 post해서 일정 인터벌 당 계속해서 호출하도록 설정하였다. 진폭값을 실시간으로 가져와서 현재 리스트의 가장 앞에 붙여서 순차적으로 그려질 수 있도록 하였다.

이때 리스트가 변경되면 invalidate()를 호출하여 뷰를 갱신해주어야 한다.

//Runnable : 반복해서 자신을 실행시키도록 요청(interface)
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)
    }
}