How to animate a String in Jetpack Compose?

Viewed 2841

I have a String "(+91)". How do I animate it such that on some action, "(+91)" fades in gradually, and on some other action "(+91)" fades out gradually. I am using "(+91)" as prefix in my Textfield, with the help of visualTransformation.

This is the code I'm using for my Textfield:

    TextField(
        value = query3.value,
        onValueChange = { newValue ->
            query3.value = newValue
            mobErrorVisible.value = false
        },

   visualTransformation = if (showCode){
                    PrefixTransformation("(+91)")} //Animate (+91)
            else
                    PrefixTransformation(""), 
        
            label = {
            Text(
                "Mobile Number",
                color = colorResource(id = R.color.bright_green),
                fontFamily = FontFamily(Font(R.font.poppins_regular)),
                fontSize = with(LocalDensity.current) { dimensionResource(id = R.dimen._12ssp).toSp() })
                
        },
        interactionSource = interactionSource,
        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
        textStyle = TextStyle(
            textAlign = TextAlign.Start,
            color = colorResource(id = R.color.bright_green),
            fontFamily = FontFamily(Font(R.font.poppins_regular)),
            fontSize = with(LocalDensity.current) { dimensionResource(id = R.dimen._16ssp).toSp() }
        ),
        modifier = Modifier
            .drawBehind {
                val strokeWidth = indicatorWidth.value * density
                val y = size.height - strokeWidth / 2
                drawLine(
                    indicatorColor,
                    Offset(TextFieldPadding.toPx(), y),
                    Offset(size.width - TextFieldPadding.toPx(), y),
                    strokeWidth
                )
            }
            .focusRequester(focusRequester)
            .onFocusChanged { showCode = (it.isFocused || query3.value != "")}
            .constrainAs(phone) {
                top.linkTo(glPhone)
                bottom.linkTo(glPhoneBottom)
                start.linkTo(glLeft)
                end.linkTo(glRight)
                width = Dimension.fillToConstraints
                height = Dimension.fillToConstraints
            },
        colors = TextFieldDefaults.textFieldColors(
            backgroundColor = Color.Transparent,
            focusedIndicatorColor = Transparent,
            unfocusedIndicatorColor = Transparent,
            disabledIndicatorColor = Transparent
        )
    )

This is my PrefixTransformation class:

 class PrefixTransformation(val prefix: String) : VisualTransformation {
    override fun filter(text: AnnotatedString): TransformedText {
        return PrefixFilter(text, prefix)
    }
}

This is my PrefixFilter() function:

fun PrefixFilter(number: AnnotatedString, prefix: String): TransformedText {

    var out = prefix + " " + number.text
    val prefixOffset = prefix.length

    val numberOffsetTranslator = object : OffsetMapping {
        override fun originalToTransformed(offset: Int): Int {
            return offset + prefixOffset
        }

        override fun transformedToOriginal(offset: Int): Int {
            if (offset <= prefixOffset-1) return prefixOffset
            return offset - prefixOffset
        }
    }

    return TransformedText(AnnotatedString(out), numberOffsetTranslator)
}

We can animate color as:

val color = remember { Animatable(Color.Gray) }
LaunchedEffect(ok) {
    color.animateTo(if (ok) Color.Green else Color.Red)
}
Box(Modifier.fillMaxSize().background(color.value))

But how do we animate a String?

2 Answers

What you need is the animatedVisibility composable. Here is an example where the visibility of the text is controlled by the button

Box(
    modifier = Modifier
        .fillMaxSize()
) {
    var visible by remember { mutableStateOf(false) }

    Button(
        modifier = Modifier.align(Alignment.TopCenter),
        onClick = {
            visible = !visible
        }
    ) {
        Text("Toggle Visibility")
    }

    val animationDuration = 2000

    AnimatedVisibility(
        modifier = Modifier.align(Alignment.BottomCenter),
        visible = visible,
        enter = fadeIn(animationSpec = tween(durationMillis = animationDuration)),
        exit = fadeOut(animationSpec = tween(durationMillis = animationDuration))
    ) {
        Text("ABC")
    }
}

A sensible solution would be to use a separate Composable for the country code. That way you can wrap the Composable in something like crossfade or just AnimatedVisibility (Experimental as of compose 1.0.0-beta07)

Related