Jetpack Compose Tutorial: Replicating Dribbble Audio App Part 1 | by Nik Afonasov | Jan, 2023 | Variable Tech

At Exyte we attempt to contribute to open-source as a lot as we will, from releasing libraries and elements to writing articles and tutorials. One kind of tutorials we do is replicating — taking a fancy UI element, implementing it utilizing new frameworks and writing a tutorial alongside. We began with SwiftUI some years in the past, however as we speak we lastly foray into Android, utilizing Google’s declarative UI framework: Jetpack Compose.

Design idea by the gifted Taras Migulko @dribbble

Our app will consist of three screens, with animated transitions between them:

For ease of studying, replicating these screens will probably be mentioned in a number of articles that cowl the next UI components and animations:

  • Waveform from the primary display
  • Motion panel from the second display
  • Collapsing Header from the third display
  • Drag gesture transition
  • Shared factor transition

We are going to begin the sequence with animating the Waveform.

Here’s what we wish to implement:

The whole factor consists of animated vertical quantity bars, spaced at a daily distance from one another. Let’s take a look at the fundamental dimensions we will probably be utilizing:

As you effectively know, the world of Android units is wealthy in screens of various sizes, and this poses the next query — learn how to current the waveform on the screens of various units in order that it takes up all of the accessible house accessible? To make this widget match the complete width of the display, you may enhance the width of the strains proportionally or you may enhance the variety of strains. We selected the second strategy to protect the crispness of the picture. On this case step one is calculating the required variety of strains. To do this, we’ll have to divide the accessible canvas width by the scale of a single quantity bar (bar width + hole width). Additionally, we have to use toInt() as an alternative of roundToInt() as a result of we solely wish to use components that totally match the scale, no matter rounding.

val rely = (canvasWidth / (barWidthFloat + gapWidthFloat)).toInt().coerceAtMost(MaxLinesCount)

Subsequent, calculate startOffset:

val animatedVolumeWidth = rely * (barWidthFloat + gapWidthFloat)
var startOffset = (canvasWidth - animatedVolumeWidth) / 2

To point out the absence of audio output, we scale back the amplitude of peak fluctuations. Let’s outline the max and min values for bar heights:

val barMinHeight = 0f
val barMaxHeight = canvasHeight / 2f / heightDivider

The heightDivider parameter will differ relying on the audio state: idle or enjoying. For easy transition between these states, we will use:

val heightDivider by animateFloatAsState(
targetValue = if (isAnimating) 1f else 6f,
animationSpec = tween(1000, easing = LinearEasing)
)

For infinite animation we use rememberInfiniteTransition(), the place animations is the listing of components that will probably be animated, and random is a property that helps us randomly change the animation time.


val infiniteAnimation = rememberInfiniteTransition()
val animations = mutableListOf<State<Float>>()
val random = bear in mind Random(System.currentTimeMillis())

Now let’s take a look at the animation. Animation of all of the bars would take a heavy toll on a cellphone’s battery life, so to save lots of assets, we solely use a small variety of Float animation values:

repeat(15) 
val durationMillis = random.nextInt(500, 2000)
animations += infiniteAnimation.animateFloat(
initialValue = 0f,
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis),
repeatMode = RepeatMode.Reverse,
)
)

To forestall the animation from repeating each 15 strains, you may set randomized initialMultipliers.

val initialMultipliers = bear in mind 
mutableListOf<Float>().apply
repeat(MaxLinesCount) this += random.nextFloat()

Now we supply out the next operations for every line:

1) Get random peak values, in our case they’re repeated each 15 instances.

2) Add initialMultipliers to currentSize to scale back the possibility of values repeating one another:

3) Use linear interpolation to easily resize the peak:

// 1
val currentSize = animations[index % animations.size].worth
// 2
var barHeightPercent = initialMultipliers[index] + currentSize
if (barHeightPercent > 1.0f)
val diff = barHeightPercent - 1.0f
barHeightPercent = 1.0f - diff

// 3
val barHeight = lerpF(barMinHeight, barMaxHeight, barHeightPercent)

After you have the size, you may draw a quantity bar (1) and calculate the offset for the subsequent quantity bar (2).

// 1 draw the bar
drawLine(
coloration = barColor,
begin = Offset(startOffset, canvasCenterY - barHeight / 2),
finish = Offset(startOffset, canvasCenterY + barHeight / 2),
strokeWidth = barWidthFloat,
cap = StrokeCap.Spherical,
)
// 2 calculate the offset for subsequent bar
startOffset += barWidthFloat + gapWidthFloat

For a greater understanding, right here is the whole thing of the mixed drawing code that runs within the loop:

repeat(rely)  index ->
val currentSize = animations[index % animations.size].worth
var barHeightPercent = initialMultipliers[index] + currentSize
if (barHeightPercent > 1.0f)
val diff = barHeightPercent - 1.0f
barHeightPercent = 1.0f - diff

val barHeight = lerpF(barMinHeight, barMaxHeight, barHeightPercent)
drawLine(
coloration = barColor,
begin = Offset(startOffset, canvasCenterY - barHeight / 2),
finish = Offset(startOffset, canvasCenterY + barHeight / 2),
strokeWidth = barWidthFloat,
cap = StrokeCap.Spherical,
)
startOffset += barWidthFloat + gapWidthFloat

This concludes implementing the variable-width animated waveform. Subsequent installment within the sequence will display implementing the Motion Panel. See you quickly!



Jetpack Compose Tutorial: Replicating Dribbble Audio App Part 1 | by Nik Afonasov | Jan, 2023

x