androidkotlincanvasandroid-jetpack-composecompose-multiplatform

How to create the Android 13 squiggly slider using Jetpack Compose?


A demo

Android 13 introduced a wavy progress bar for its media controls in quick settings.

How can I recreate it in Jetpack Compose and Compose Multiplatform?

Here is a related question for Flutter and Dart: Squiggly Seekbar with Animation in Flutter


Solution

  • TLDR: There is a library for that: Wavy slider.
    Disclosure: I'm the author of the library.

    There are two methods to draw this wavy pattern.

    1. Using Bézier curves (implemented in version 0.5.0 and older of the library)
      (sequentially specifying two nodes/vertices with one or two control points)
    2. Using the trigonometric sine or cosine functions
      (progressing pixel by pixel horizontally and drawing the (x, sine(x)) point)

    Android 13 and 14 has used Bezier curves (here is source code of Android 14 squiggly progress).

    Using Bezier curves

    There are two variations of the Bezier curve (that I know of):

    1. Quadratic Bezier curve (two nodes and one control point)
      (called quadratic because its equation contains a degree 2 polynomial)
    2. Cubic Bezier curve (two nodes and two control points)
      (called cubic because its equation contains a degree 3 polynomial)

    So, a simple way to draw the wave is to use cubic Bezier curves. Start the x at 0. Then call cubicRelativeTo with three arguments: control point 1, control point 2, and the next point. Place the first control in the (middle, waveHeight) and the other in (middle, -waveHeight) and the next point in waveLength. Set the x to waveLength and repeat this for the entire length of the whole wave (x < totalWaveLength).

    The downside is that, the actual wave height is less than the value provided. To get an accurate height, this post will probably help.

    See this comprehensive guide about Bezier curves: https://pomax.github.io/bezierinfo/

    Using trigonometric sine or cosine functions

    It does not matter which one of the two functions to use (it only affects the initial phase aka position of the wave). We are going to use the sine function.

    So, progressing the x from 0 to sliderTotalWidth pixels (in a simple for loop), and given the following:

    radians = (x + waveShiftPx) / L * (2 * 3.14)

    y = (sin(radians) * H + S) / 2

    And then can call the canvas lineTo(x, y) for each pair of x and y.

    Then you just need to infinitely animate the wave shift between 0 and L with your desired speed (duration) and provide that animated value as waveShiftPx.

    To see the complete implementation, see the core file of the library that does the drawing of the wave.