Android

Fazendo Parecer Vivo

O projeto JellyFab busca criar uma animação suave e realista em botões ao expandir menus, utilizando 36 pontos distribuídos uniformemente ao redor da circunferência para simular a deformidade de um material elástico. Em vez de mover todo o contorno do círculo, apenas os pontos próximos à expansão são deslocados, criando uma ilusão de superfície macia e natural.

Compartilhar
Medium

Fazendo Parecer Vivo

Até este ponto, o menu se abre, os botões expandem suavemente e a animação escalonada parece polida. Do ponto de vista funcional, estamos prontos. No entanto, algo ainda não está certo. O que falta é um círculo que localmente se arredonda em direção a cada item emergente, dando-lhe o "jelly" pelo qual todo o projeto é nomeado.

Pensando no Problema de Forma Diferente

Meu primeiro instinto foi construir um Shape personalizado. O Compose já nos permite definir contornos arbitrários, então isso parecia o lugar natural para começar.

Rapidamente ficou claro que não era a abstração certa. Um Shape descreve o que um contorno parece. Mas o que eu precisava era algo que pudesse decidir como partes individuais daquele contorno deveriam se mover em cada quadro.

Não anime uma forma... Anime os pontos que a definem...

Assim que comecei a pensar dessa forma, a solução ficou muito mais simples.

Representando um Círculo como Dados

Ao invés de tratar o botão como um círculo, JellyFab trata-o como uma coleção de pontos espaçados uniformemente ao longo do seu contorno.

val sampleCount = 36

val points = MutableList(sampleCount) { index ->
val angle =
-Math.PI / 2 +
index * (2 * Math.PI / sampleCount)

Offset(
(cx + baseRadius * cos(angle)).toFloat(),
(cy + baseRadius * sin(angle)).toFloat()
)
}

Se conectássemos esses pontos, simplesmente redesenharíamos o círculo original. A parte interessante vem a seguir. Em vez de mover toda a forma, apenas movemos os pontos mais próximos do botão de ação que está se expandindo. Essa única decisão é o que cria a ilusão de uma superfície macia.

36 pontos espaçados uniformemente no Círculo

Esticando a Superfície

Cada ação primária já tem um ângulo. Esses mesmos ângulos agora se tornam direções de deformação. À medida que o item do menu expande, os pontos próximos são empurrados para fora enquanto o resto do círculo permanece quase perfeitamente imóvel.

val push = baseRadius * bounceFactor * value

for (point in points) {
val direction = point - center
val angle = atan2(direction.y, direction.x)
val difference = normalizeAngleRad(angle - bulgeAngle)
val fall = exp(-(diff * diff) / (2 * 0.6f * 0.6f))
val len = sqrt(direction.x * direction.x + direction.y * direction.y)
val newLen = len + push * fall
...
}

Observe o que está acontecendo aqui. Agora, estamos pensando em como cada ponto se relaciona com a direção de um bulge. Cada ponto faz uma pergunta simples:

Quão perto estou da direção que está se expandindo?

Quanto mais próximo estiver, mais ele se move. Quanto mais distante estiver, menos muda. Tudo o resto vem dessa ideia.

Pontos próximos à direção do bulge com a faixa de influência

Por que uma curva Gaussiana?

Uma interrupção abrupta produziria algo como isso:

████████░░░░░░░░

Todos os pontos dentro da região se moveriam a mesma quantidade. Todos os pontos fora dela não se moveriam em absoluto. O resultado seria mecânico. Em vez disso, JellyFab usa uma queda Gaussiana.

val falloff = exp(-(difference * difference) / (2 * sigma * sigma))

Se você já aplicou um desfoque no Photoshop ou usou uma curva em forma de sino na estatística, já viu essa função antes. A mesma ideia funciona perfeitamente aqui.

  • Pontos diretamente à frente da ação de expansão recebem toda a empurrada.
  • Pontos ligeiramente ao lado recebem uma empurrada menor.
  • Pontos do lado oposto do botão mal se movem em absoluto.

Em vez de criar um saliência aguda, a deformação se mescla naturalmente com o resto do círculo.

Desloque os pontos selecionados para fora. A quantidade de empurrão é maior no centro e diminui em direção à borda

Do Ponto de Volta para um Blob

Após a deformação, o botão não é mais um círculo. São apenas 36 pontos deslocados. Conectá-los com linhas retas funciona tecnicamente... mas o resultado parece como um modelo low-poly.

Em vez disso, os pontos são costurados juntos usando curvas quadráticas de Bézier.

val path = Path().apply {
moveTo(points.first().x, points.first().y)
for (...) {
quadraticTo(...)
}

close()
}

Ao invés de desenhar do ponto para o ponto, cada segmento se curva suavemente através do ponto anterior antes de alcançar a metade do próximo. O resultado é uma superfície contínua que parece orgânica em vez de poligonal.

Fonte original

Conteúdo traduzido e adaptado pela redação do Notícias Mobile. Confira também a matéria na fonte original.

Leia a matéria completa