0

I need some help animating the entire UI of my project for android TV, based on some live data.

The following the structure I have gone for:

@Composable
fun MainPageUI(){
    var isClicked by remember { mutableStateOf(false) }
    Scaffold(topBar = {}) { paddingValues ->
        Column(modifier = Modifier.padding(paddingValues)){
            Box(Modifier.weight(0.50f)){
               //Code for some banner
            }
            Column(modifier = Modifier.weight(0.50f)){
                Row {
                    //Code for menu bar
                }
                Box {
                    //Code for main content
                }
            }
        }
    }
}

What I want to do is that when some live data is received via isClicked, I want to slide the entire UI up so that the banner section is only partially visible, say 10% of the screen, and the rest is occupied by the rest of the content (menu and content section).

I tried:

val offsetAnimation: Dp by animateDpAsState(if (isRecClicked) (-200).dp else 0.dp)

and then setting this as the absoluteoffset in the modifier of the banner section and other section, but that just leaves a dark rectangle at the bottom of the screen, 200 dp tall. What I want is to fill this section with the content section of my app, to show more content.

Is there a way to do this?

vighnesh153
  • 4,354
  • 2
  • 13
  • 27
Rahul Rawat
  • 103
  • 6

1 Answers1

0

I would recommend using AnimatedContent for this. It enables you to specify virtually any kind of sliding animation and wrap composable functions into it. For example:

setContent {
        var isClicked by remember { mutableStateOf(false) }
        AnimatedContent(
            targetState = isClicked,
            transitionSpec = {
                val durationMillis = 1000
                if (targetState != initialState && targetState) {
                    (slideInVertically(animationSpec = tween(durationMillis)) { height -> height })
                        .with(slideOutVertically(animationSpec = tween(durationMillis)) { height -> -height })
                } else {
                    slideInVertically(animationSpec = tween(durationMillis)) { height -> -height } with
                            slideOutVertically(animationSpec = tween(durationMillis)) { height -> height }
                }.using(
                    SizeTransform(clip = false)
                )
            }
        ) { buttonClicked ->
            if (buttonClicked) {
                Column(
                    Modifier
                        .fillMaxSize()
                ) {
                    Text(
                        "Some content",
                        modifier = Modifier
                            .fillMaxSize()
                            .background(Color.White)
                            .padding(top = 100.dp),
                        fontSize = 26.sp
                    )
                }
            } else {
                Row(Modifier.fillMaxSize()) {
                    Column(
                        Modifier
                            .fillMaxWidth()
                            .align(Alignment.CenterVertically)
                    ) {
                        Button(
                            onClick = { isClicked = true },
                            modifier = Modifier
                                .align(Alignment.CenterHorizontally)
                        ) {
                            Text("Click me!")
                        }
                    }
                }
            }
        }
    }

Click here to see this UI as a GIF

Another way to achieve this is to use AnimatedVisibility. For more information, see the docs: https://developer.android.com/jetpack/compose/animation/composables-modifiers#animatedcontent https://developer.android.com/jetpack/compose/animation/composables-modifiers#animatedvisibility

If you want to keep both parts of the UI on the screen, you can also leverage an in-built class Animatable like this:

setContent {
    with(LocalDensity.current) {
        val screenHeight = LocalConfiguration.current.screenHeightDp.dp.toPx()
        var isClicked by remember { mutableStateOf(false) }
        val topBarHeight = remember {
            androidx.compose.animation.core.Animatable(screenHeight)
        }

        LaunchedEffect(isClicked) {
            if (isClicked) {
                topBarHeight.animateTo(
                    targetValue = 300f,
                    animationSpec = tween(durationMillis = 1000, easing = FastOutSlowInEasing)
                )
            }
        }

        Column(
            Modifier
                .fillMaxSize()
                .background(Color.LightGray),
            verticalArrangement = Arrangement.Bottom
        ) {
            Column(
                modifier = Modifier
                    .background(
                        Brush.horizontalGradient(
                            listOf(Color.Magenta, Color.Blue)
                        )
                    )
                    .fillMaxWidth()
                    .height(topBarHeight.value.toDp()),
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Button(
                    onClick = { isClicked = true },
                    modifier = Modifier,
                    colors = ButtonDefaults.buttonColors(
                        backgroundColor = Color.DarkGray,
                    )
                ) {
                    Text("Click me!", color = Color.White)
                }
            }
            Column(
                Modifier
                    .fillMaxWidth()
                    .height((screenHeight - topBarHeight.value).toDp())
            ) {
                Text(
                    "Some content",
                    modifier = Modifier
                        .fillMaxSize()
                        .background(Color.White),
                    fontSize = 26.sp
                )
            }
        }

    }
}

What this code effectively does is taking the height of the screen and animating that value until it reaches a certain value that you choose, I've chosen 300 pixels. Its only disadvantage is that you cannot animate an integer (and, consequently, a Dp). Mind that the animation should be launched in a separate coroutine with LaunchedEffect.

Again, check the GIF for the result:

Animation with top bar

  • This looks like the correct approach, but I would like to control how much my UI is sliding up or down. So in my case, the banner sections slides up, the menu section moves with it and the he increased space is now taken up by content section. – Rahul Rawat Mar 30 '23 at 13:34
  • @RahulRawat, I've updated the answer, now I think that it suits your intention with the UI. – Глеб Гутник Mar 31 '23 at 15:40