Creating A Transition Animation in Android Compose

This post will be showing you how I created a basic transition animation in Android Compose.

The idea of using a transition animation is if there is a UI feature that you want to change which involves changing multiple elements. You could utilize other methods than this but for my situation, I decided to use it for the simplicity of development.

In my example, I wanted to create a TextField that is not focused off screen, then when a button is pressed:

  1. Slide the elements parent container into a location on screen, in this case a Row
  2. Also change the visibility of the rest of the container elements by expanding/shrinking

Code

This is the MainActivity that points to the custom UI element of SlideInOverlay. The SlideInOverlay has a callback that captures the text written in TextField back to the parent element to display in the parent elements Text element on screen. Simple. The callback could be used to send elsewhere depending on your situation.

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            SlidingInMessageTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    var message by remember { mutableStateOf("") }
                    Box(
                        modifier = Modifier
                            .fillMaxSize()
                            .padding(innerPadding)
                    ) {
                        Text(modifier = Modifier.align(Alignment.Center), text = message)
                        SlideInOverlay(
                            callback = { msg ->
                                message = msg
                            }
                        )

                    }

                }
            }
        }
    }
}

The SlideInOverlay function contains a root element of box that has the Row element inside. It is positioned to bottom right corner using Offsets. When the IconButton is clicked, then the transition animation executed based on the state provided.

@Composable
fun SlideInOverlay(callback: (String) -> Unit) {

    val density = LocalDensity.current
    var size by remember { mutableStateOf(Size.Zero) }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .onGloballyPositioned {
                size = Size(
                    with(density) { it.size.width.toDp() }.value,
                    with(density) { it.size.height.toDp() }.value
                )
            }
    ) {

        var slideInState by remember { mutableStateOf(false) }
        val transition = updateTransition(slideInState)
        val slidingIn by transition.animateOffset(transitionSpec = { tween(750) }) {
            if (!it) Offset(size.width - 48f, 0f) else Offset(0f, -size.height / 3)
        }

        var message by remember { mutableStateOf("") }

        if(!slideInState){
            message = ""
        }

        Row(
            modifier = Modifier
                .fillMaxWidth()
                .offset(x = slidingIn.x.dp, y = slidingIn.y.dp)
                .align(Alignment.BottomEnd)
        ) {
            IconButton(
                modifier = Modifier
                    .size(48.dp)
                    .clip(shape = CircleShape),
                onClick = {
                    slideInState = !slideInState
                }) {
                Icon(imageVector = Icons.Sharp.Email, contentDescription = null)
            }
            transition.AnimatedVisibility(
                visible = { slideInState -> slideInState },
                enter = expandHorizontally(tween(1000)),
                exit = shrinkHorizontally(tween(500))
            ) {
                TextField(
                    modifier = Modifier.width(IntrinsicSize.Max).focusable(slideInState),
                    value = message,
                    onValueChange = {
                        message = it
                        callback(message)
                    })

            }
            IconButton(
                modifier = Modifier
                    .size(48.dp)
                    .clip(shape = CircleShape),
                onClick = {

                }) {
                Icon(imageVector = Icons.AutoMirrored.Sharp.Send, contentDescription = null)
            }
        }

    }
}

Feel free to give it a try and update depending on your use case.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *