Speech waves — an Android Canvas Animation

Nikolai Shevchenko
6 min readJun 8, 2020

Although Android is not named after a dessert 🍭 anymore, we can still get some sweetness out of it. In this article, we’ll dive into the drawing of a custom view with Canvas API which reacts to the user’s microphone.

The idea behind it is to give a clear and appealing feedback to the user’s pronouncing word or sentences for our speech recognition exercises. After a bit of research I couldn’t find anything similar in the Android world, apart from simpler animations like Siri waves or simple round button scaling up and down.

Here’s the prototype made by our designer with After Effects, using the Audio Spectrum library which has the animation built in, which looks quite sophisticated.

Prototype in Adobe After Effects — Audio Spectrum library

However, due to time constraints we decided to:

  • leave behind the smoothened edges between the intersection points of arcs as this would have required to draw a cubic path around a set of coordinates.
  • keep the outer layer of the component simpler by having three layers with gradients on top of each other, still providing depth and smoothness.

In the end here’s what we are aiming to achieve, still maintaining the randomness of width and height of each arc, manipulated by the amplitude of the mic 🎤.

The expanded state of the animation (maximum amplitude of the voice)

Let’s begin by defining the tools we’ll be using and dive into some coding! 🎉

Canvas API 🎨

The Canvas API is an advanced 2D graphics library for Android. It provides methods to draw primitive shapes that can be manipulated to achieve more complex structures. Every View has a canvas in which it gets drawn and we can access it overriding the fun onDraw(Canvas canvas) of the the View. With it we can draw Text, Lines, Ovals, Arcs, Bitmaps, etc.. by providing x/y coordinates and a Paint object used to define colour and style.

If you’re not familiar with the Canvas API, there are some great articles by

Getting Started with Android Canvas Drawing and Android Canvas Drawing: Useful Graphics Classes & Operations.

Chronology of the animation

In order to start coding, we’ll break down the preparation steps to achieve the shape of this component and its code implementation:

  1. Choose random angles of arcs around the circle
  2. Re-calculate the heights for each arc
  3. Animate them up and down multiplied by the voiceAmplitude
  4. Animate, Go to step 1 & Repeat

Choose Random Angles

We calculate random widths of the arcs around the circumference. Once the average angle is set between 40 and 60 degrees, we’ll keep adding the different values to the angles list until we reach a full rotation of 360 degrees.

Choose Arc’s heights

Each of the arcs will have a random height, multiplied by the current Amplitude of the mic (higher the amplitude = taller the arcs). After the first iteration we noticed that increasing the height randomly of every single arc seemed a bit visually overwhelming. Thus, we decided to increase the height of only one random dominant and two subdominants next to it, while the rest of all arcs would remain small. This made our animation seem more composed and simpler. The dominantIndex is our dominant arc’s index multiplied by the voice amplitude.

Drawing the shape

Evolution of the drawing process of the shape

The arcs can be simply drawn as ovals, as we only have to provide the top, left, right, bottom coordinates to draw one, without caring about the sweeping and starting angle for the arcs, since we don’t see what’s behind the big play button. We could optimise this step by clipping the ovals with the round button shape on top, but the GPU performance later will indicate that this is not necessary as the frame rate is constantly stable. Let’s look at the code snippet of the shape and walk through it.

The top value takes into account the circle’s total radius (as there will be three of them — three layers), and everything gets multiplied by the intensity of the amplitude of the microphone. bottom is always the centre of the circle slightly shifted downwards by the delta height of the circle so that the stretching sides will always be aligned with the intersection of the centre button.

Finally, to obtain the right value we calculate the sin(angle) given half of the angle obtained earlier. The left is the negative mirrored coordinate of right.

We drew the top circle. Now, the next one should be oblique. The only caveat of ovals and arcs is that they cannot be drawn oblique, and to solve this, we trick the canvas by rotating it after drawing every arc with canvas.rotate(). Also, let’s not forget to call canvas.restore() to clear the previously drawn arc. This implies we’ll be rotating the canvas by an accumulation of all the angles we drew so far. After a whole cycle we will rotate the canvas by 360 degrees returning to the original position. Here’s how we do it

Since the ovals can only be drawn vertically, we need to rotate the canvas by half of the previous angle and half of the current angle in order to make them touch side by side.

Last thing left is to replicate the whole drawing three times to give a sense of depth. If you wondered what was the layerN variable that affected the height (top value) of the drawing oval — it is the multiplier of the N-th layer of the component. In fact, we run a loop 3 times, starting from top to bottom with layerN = 3, drawing a bigger shape and finishing with layerN = 1, reaching the original size. In fact, in the last code snippet, the wavePaint object responsible for defining the filled colour of the ovals, is assigned a different colour on every loop, from the colorsList array.

And here’s the final transformations:

Final stages of drawing evolution

Give it some life — Animating the shape

In order to animate the shape, a ValueAnimator. As long as the microphone is on, the animator keeps pulsating simply going from 0 to 10 and back to 0. The repeatMode = REVERSE along with repeatCount = INFINITE specify the animation keeps pulsating non stop until the animator is canceled. Every cycle of the animation, the shape’s angles and random dominant height will be recalculated. And Here’s the code:

To make the shape even more lively, we decided to animate the colorsList which define the shape’s colour scheme, to even darker colours, when the amplitude of the microphone is high enough. This makes the speech waves much visually reactive. Here’s what is achieved after implementing all the steps.

Final result of the animation

Pretty, but quite not there yet..

This animation can be found already in our beloved Busuu app and many people seem to like it, however it’s far from the After Effects prototype. Mainly because we used specific oval shapes to build this, missing out on rounded edges in between the waves and just the magic of fluidity.

In order to bring it to the next level, we should try and change our approach from drawing ovals, to have one equation that could output X and Y values for each angle of the circle. Then we could draw an ArcPath around those points for each frame, creating a more fluid experience. That is a more interesting approach, which I already started collaborating on with my data scientists. More news will follow!

Thank you! 👋

--

--

Nikolai Shevchenko

Android Engineer at Depop. Love drumming, composing and just creating things.