Estou tentando criar um efeito de texto no Jetpack Compose onde o texto usa um pincel que parece um conjunto de cores mudando suavemente na diagonal. As letras individuais do texto devem mover-se para cima e para baixo, bem como dar meias voltas.
Eu encontrei dois problemas:
- Se tento aplicar o pincel na palavra inteira, não sei como animar as letras que se movem e giram. Aqui está o código que tentei:
Text(
text = word,
fontSize = fontSize,
style = LocalTextStyle.current.copy(brush = gradientBrush),
modifier = modifier
)
- Se eu aplicar a animação em cada letra individualmente, consigo obter o resultado desejado com a animação, mas o pincel também é aplicado em cada letra individualmente, criando um efeito pouco atraente. Preciso que o pincel seja aplicado em toda a palavra. Aqui está o código que tentei:
Row {
word.forEachIndexed { index, char ->
val angle by rotationAngles[index]
val offset by letterOffsets[index]
Text(
text = char.toString(),
fontSize = fontSize,
style = LocalTextStyle.current.copy(brush = gradientBrush),
modifier = Modifier
.rotate(angle)
.offset(y = offset.dp)
)
}
}
Aqui estão duas demonstrações:
- Resultado desejado de aplicar o pincel na palavra inteira, mas não sei animar as letras.
- Resultado desejado da aplicação da animação, mas não consigo aplicar o pincel na palavra inteira (o pincel é aplicado em cada letra individualmente).
Como posso criar uma animação de movimento para cima, para baixo e meio giro para cada letra individualmente, enquanto aplico o pincel à palavra inteira no Jetpack Compose?
Aqui está o código completo:
@Preview(showBackground = true)
@Composable
fun RotatingLettersPreview() {
RotatingLetters("Medium")
}
@OptIn(ExperimentalTextApi::class)
@Composable
fun RotatingLetters(word: String,
fontSize: TextUnit = TextUnit.Unspecified,
modifier: Modifier = Modifier) {
val colorGradient = listOf(
Color(0xFFC0EFFF), // Light blue
Color(0xFF9BDBFB), // Medium blue
Color(0xFF75C2F9), // Dark blue
Color(0xFFF7D7C4), // Light pink
Color(0xFFF5B2C7), // Medium pink
Color(0xFFF28BAE), // Dark pink
Color(0xFFF7F2D4), // Light beige
Color(0xFFF5E0C3), // Medium beige
)
val fontSizeInPx = with(LocalDensity.current) { 30.sp.toPx() }
val doubleFontSizeInPx = fontSizeInPx * 2
val infiniteTransitionForOffset = rememberInfiniteTransition(label = "")
val offsetAnimation by infiniteTransitionForOffset.animateFloat(
initialValue = 0f,
targetValue = doubleFontSizeInPx,
animationSpec = infiniteRepeatable(tween(200000, easing = LinearEasing)), label = ""
)
val gradientBrush = remember(offsetAnimation) {
object : ShaderBrush() {
override fun createShader(size: Size): Shader {
val widthOffset = size.width * offsetAnimation
val heightOffset = size.height * offsetAnimation
return LinearGradientShader(
colors = colorGradient,
from = Offset(widthOffset, heightOffset),
to = Offset(widthOffset + size.width, heightOffset + size.height),
tileMode = TileMode.Mirror
)
}
}
}
val infiniteTransitionForRotation = rememberInfiniteTransition(label = "")
val rotationDirection = remember { mutableStateOf(1) }
val rotationAngles = List(word.length) { index ->
infiniteTransitionForRotation.animateFloat(
initialValue = 0f,
targetValue =
if (index % 2 == 0) 5f * rotationDirection.value else -5f * rotationDirection.value,
animationSpec = infiniteRepeatable(
animation = tween(250, easing = LinearEasing),
repeatMode = RepeatMode.Restart
), label = ""
)
}
LaunchedEffect(key1 = Unit) {
while (true) {
delay(250)
rotationDirection.value *= -1
}
}
val letterOffsets = List(word.length) { index ->
infiniteTransitionForRotation.animateFloat(
initialValue = 0f,
targetValue = if (index % 2 == 0) 2f else -2f,
animationSpec = infiniteRepeatable(
animation = tween(250, easing = LinearEasing),
repeatMode = RepeatMode.Reverse
), label = ""
)
}
Text(
text = word,
fontSize = fontSize,
style = LocalTextStyle.current.copy(brush = gradientBrush),
modifier = modifier
)
/*
Row {
word.forEachIndexed { index, char ->
val angle by rotationAngles[index]
val offset by letterOffsets[index]
Text(
text = char.toString(),
fontSize = fontSize,
style = LocalTextStyle.current.copy(brush = gradientBrush),
modifier = Modifier
.rotate(angle)
.offset(y = offset.dp)
)
}
}
*/
}
você pode obter um efeito semelhante usando uma combinação de drawWithContent e recortando cada letra para criar um único gradiente contínuo em todas as letras. Aqui está uma solução conceitual:
Crie um elemento de composição personalizado que use o Canvas para desenhar o texto manualmente, permitindo aplicar um único pincel gradiente em toda a palavra. Use drawWithContent para recortar a área de desenho até os limites de cada caractere ao desenhá-los, para que cada um deles possa ser animado de forma independente enquanto ainda compartilham o mesmo pincel. Aqui está um exemplo de como você pode implementá-lo: