Tutorial do Jetpack Compose: replicando o aplicativo de áudio Dribbble, parte 1
Implementando uma IU totalmente personalizada com animações complexas: Waveform Animation
Na Exyte , tentamos contribuir com o código aberto o máximo possível, desde o lançamento de bibliotecas e componentes até a escrita de artigos e tutoriais. Um tipo de tutorial que fazemos é a replicação — pegando um componente de interface do usuário complexo, implementando-o usando novas estruturas e escrevendo um tutorial ao lado. Começamos com o SwiftUI há alguns anos, mas hoje finalmente entramos no Android, usando o framework declarativo de interface do usuário do Google: Jetpack Compose.
Estrutura da Tela
Nosso aplicativo será composto por 3 telas, com transições animadas entre elas:
Para facilitar a leitura, a replicação dessas telas será discutida em vários artigos que abrangem os seguintes elementos e animações da interface do usuário:
- Forma de onda da primeira tela
- Painel de ação da segunda tela
- Recolher o cabeçalho da terceira tela
- Transição de gesto de arrastar
- Transição de elemento compartilhado
Criando a forma de onda
Aqui está o que queremos implementar:
Todo o elemento consiste em barras de volume verticais animadas, espaçadas a uma distância regular umas das outras. Vejamos as dimensões básicas que usaremos:
Como você bem sabe, o mundo dos dispositivos Android é rico em telas de tamanhos diferentes, e isso levanta a seguinte questão - como apresentar a forma de onda nas telas de diferentes dispositivos para que ocupe todo o espaço disponível? Para fazer este widget caber em toda a largura da tela, você pode aumentar a largura das linhas proporcionalmente ou pode aumentar o número de linhas. Escolhemos a segunda forma para preservar a nitidez da imagem. Neste caso, o primeiro passo é calcular o número necessário de linhas. Para fazer isso, precisaremos dividir a largura disponível da tela pelo tamanho de uma única barra de volume (largura da barra + largura do intervalo). Além disso, precisamos usar toInt()em vez de roundToInt()porque queremos usar apenas elementos que se ajustam totalmente ao tamanho, independentemente do arredondamento.
val count = (canvasWidth / (barWidthFloat + gapWidthFloat)).toInt().coerceAtMost(MaxLinesCount)
val animatedVolumeWidth = count * (barWidthFloat + gapWidthFloat)
var startOffset = (canvasWidth - animatedVolumeWidth) / 2
val barMinHeight = 0f
val barMaxHeight = canvasHeight / 2f / heightDivider
val heightDivider by animateFloatAsState(
targetValue = if (isAnimating) 1f else 6f,
animationSpec = tween(1000, easing = LinearEasing)
)
val infiniteAnimation = rememberInfiniteTransition()
val animations = mutableListOf<State<Float>>()
val random = remember { Random(System.currentTimeMillis()) }
repeat(15) {
val durationMillis = random.nextInt(500, 2000)
animations += infiniteAnimation.animateFloat(
initialValue = 0f,
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis),
repeatMode = RepeatMode.Reverse,
)
)
}
val initialMultipliers = remember {
mutableListOf<Float>().apply {
repeat(MaxLinesCount) { this += random.nextFloat() }
}
}
1) Obtenha valores aleatórios de altura, no nosso caso eles são repetidos a cada 15 vezes.
2) Adicione initialMultiplierspara currentSizereduzir a chance de valores se repetirem:
3) Use interpolação linear para redimensionar suavemente a altura:
// 1
val currentSize = animations[index % animations.size].value
// 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)
// 1 draw the bar
drawLine(
color = barColor,
start = Offset(startOffset, canvasCenterY - barHeight / 2),
end = Offset(startOffset, canvasCenterY + barHeight / 2),
strokeWidth = barWidthFloat,
cap = StrokeCap.Round,
)
// 2 calculate the offset for next bar
startOffset += barWidthFloat + gapWidthFloat
repeat(count) { index ->
val currentSize = animations[index % animations.size].value
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(
color = barColor,
start = Offset(startOffset, canvasCenterY - barHeight / 2),
end = Offset(startOffset, canvasCenterY + barHeight / 2),
strokeWidth = barWidthFloat,
cap = StrokeCap.Round,
)
startOffset += barWidthFloat + gapWidthFloat
}





































![O que é uma lista vinculada, afinal? [Parte 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)