Home > Net >  Animate canvas items that are based on a constantly changing list state
Animate canvas items that are based on a constantly changing list state

Time:07-17

TL;DR: How would I animate multiple item heights within the Canvas. Like the height of these spikes in a waveform.

A little back story: I'm writing an app to display the waveform of an ongoing audio recording. I'm taking the bytes directly from an AudioRecord and calculating the amplitude every 1000ish samples and pushing them to a list that is then observed in a composable.

The usage within the composable looks something like this:

@Composable
fun HomeScreen(vm: HomeViewModel = hiltViewModel()) {
    val data: SnapshotStateList<Int> = GlobalState.waveformBuffer

    Canvas(
            modifier = Modifier.fillMaxWidth().height(250.dp)
    ) {
         val canvasHeight = this.size.height

         data.forEachIndexed { i, value ->
            val spikeHeight = if (value > 0) value / 30f else 9f

            val spikeSize = Size(12f, spikeHeight)
            val spikeOffset = Offset(
                (i * (spikeSize.width   6f)),
                canvasHeight / 2f - spikeSize.height / 2f
            )
            val spikeColor = Color(0xFFFF0000)

            drawRoundRect(
                size = spikeSize,
                topLeft = spikeOffset,
                color = spikeColor,
                cornerRadius = CornerRadius(2f, 2f)
            )
        }
    }
}

I am now trying to animate each line as it's being drawn to the canvas for the first time. The Google Recorder is a good example of this where each of the audio spikes "grows" to its designated height. Here's the Google Recorder example.

I am basically trying to understand how I would animate the spikes just like they did. I've tried storing a whole lot of Animatables and using those but the state updates way too fast for it to be an efficient solution and the app just ended up crashing every time.

CodePudding user response:

I don't know anything about Compose, but it looks like you already know how to use it to draw bars to a Canvas? It might help to think of that video as a fixed set of positions - after a certain point horizontally, the bars are drawn at their full height. Before that, each position is drawn at a fraction of its full height - say 0%, 20%, 40%, 60%, 80%, 100%, 100%, 100%... As it "scrolls" and each spike is drawn further to the right, it's drawn closer to its actual height, until it's being drawn normally.

Something like this:

data.forEachIndexed { i, value ->
    val spikeHeight = if (value > 0) value / 30f else 9f
    val drawnHeight = (spikeHeight * (i / 5f)).coerceAtMost(1f)

or you could use an interpolator to get smoother curves in the changes (so it doesn't just ramp from 0 to 100% although that might look fine!)

  • Related