From 866ca38ed67f7ee254183b9d7011881d09b6b36a Mon Sep 17 00:00:00 2001 From: AbhinavAppentus Date: Sat, 18 Jun 2022 21:31:14 +0530 Subject: [PATCH] made animated bottom navigation like original disney motions app https://medium.com/@thegoldycopythat/animated-bottom-navigation-in-jetpack-compose-af8f590fbeca --- .../ui/custom/AnimatedBottomNav.kt | 158 ++++++++++++++++++ .../disneycompose/ui/posters/Posters.kt | 42 +++-- 2 files changed, 182 insertions(+), 18 deletions(-) create mode 100644 app/src/main/java/com/skydoves/disneycompose/ui/custom/AnimatedBottomNav.kt diff --git a/app/src/main/java/com/skydoves/disneycompose/ui/custom/AnimatedBottomNav.kt b/app/src/main/java/com/skydoves/disneycompose/ui/custom/AnimatedBottomNav.kt new file mode 100644 index 0000000..32e2734 --- /dev/null +++ b/app/src/main/java/com/skydoves/disneycompose/ui/custom/AnimatedBottomNav.kt @@ -0,0 +1,158 @@ +package com.skydoves.disneycompose.ui.custom + +import android.annotation.SuppressLint +import androidx.compose.animation.* +import androidx.compose.animation.core.* +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavGraph.Companion.findStartDestination +import androidx.navigation.NavHostController +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController +import com.skydoves.disneycompose.R +import com.skydoves.disneycompose.ui.posters.DisneyHomeTab +import com.skydoves.disneycompose.ui.theme.purple200 + +/** + * @author abhianv chouhan + * @link https://medium.com/@thegoldycopythat/animated-bottom-navigation-in-jetpack-compose-af8f590fbeca + */ +@Composable +fun AnimatedBottomNav( + modifier: Modifier = Modifier, + tabs: Array = DisneyHomeTab.values(), + selectedTab: DisneyHomeTab, + onItemClick: (tab:Int) -> Unit, +) { + + Box { + var width by remember { mutableStateOf(0f) } + var currentIndex by remember { mutableStateOf(0) } + val offsetAnim by animateFloatAsState( + targetValue = when (currentIndex) { + 1 -> width / 2f - with(LocalDensity.current) { 50.dp.toPx() } + 2 -> width - with(LocalDensity.current) { 100.dp.toPx() } + else -> 0f + } + ) + BottomNavigation( + modifier = modifier + .fillMaxWidth() + .onGloballyPositioned { + width = it.size.width.toFloat() + }, + backgroundColor = purple200 + ) { + tabs.forEachIndexed { index, tab -> + AnimatedBottomNavigationItem( + label = tab.title, + icon = tab.icon, + selected = tab == selectedTab, + onClick = { + onItemClick(tab.title) + currentIndex = index + } + ) + } + } + Box( + modifier = Modifier + .width(100.dp) + .height(3.dp) + .offset(with(LocalDensity.current) { offsetAnim.toDp() }, 0.dp) + .clip(RoundedCornerShape(5.dp)) + .background(Color.White) + ) + } +} + +@SuppressLint("CoroutineCreationDuringComposition") +@Composable +fun AnimatedBottomNavigationItem( + selected: Boolean, + onClick: () -> Unit, + icon: ImageVector, + modifier: Modifier = Modifier, + enabled: Boolean = true, + label: Int, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + selectedContentColor: Color = LocalContentColor.current, + unselectedContentColor: Color = LocalContentColor.current +) { + + val top by animateDpAsState( + targetValue = if (selected) 0.dp else 56.dp, + animationSpec = SpringSpec(dampingRatio = 0.5f, stiffness = 200f) + ) + Box( + modifier = Modifier + .height(56.dp) + .padding(start = 30.dp, end = 30.dp) + .clickable( + interactionSource = interactionSource, + indication = null + ) { + onClick.invoke() + }, + contentAlignment = Alignment.Center, + ) { + Icon( + imageVector = icon, + tint = selectedContentColor, + + contentDescription = null, + modifier = Modifier + .height(56.dp) + .width(26.dp) + .offset(y = top) + ) + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .height(56.dp) + .offset(y = top - 56.dp) + ) { + NormalBoldText( + text = stringResource(id = label), + ) + } + } +} + + +data class BottomNavItem( + val title: String, + val icon: Int, + val route: String, +) + + +@Composable +fun NormalBoldText(text: String, modifier: Modifier = Modifier) { + Text( + text = text, + fontSize = 14.sp, + fontWeight = FontWeight.Bold, + color = Color.White, + textAlign = TextAlign.Center, + modifier = modifier + ) +} diff --git a/app/src/main/java/com/skydoves/disneycompose/ui/posters/Posters.kt b/app/src/main/java/com/skydoves/disneycompose/ui/posters/Posters.kt index 2283fe2..5f2ee21 100644 --- a/app/src/main/java/com/skydoves/disneycompose/ui/posters/Posters.kt +++ b/app/src/main/java/com/skydoves/disneycompose/ui/posters/Posters.kt @@ -51,6 +51,7 @@ import com.google.accompanist.insets.navigationBarsHeight import com.google.accompanist.insets.navigationBarsPadding import com.skydoves.disneycompose.R import com.skydoves.disneycompose.model.Poster +import com.skydoves.disneycompose.ui.custom.AnimatedBottomNav import com.skydoves.disneycompose.ui.main.MainViewModel import com.skydoves.disneycompose.ui.theme.purple200 @@ -72,25 +73,30 @@ fun Posters( modifier = Modifier.constrainAs(body) { top.linkTo(parent.top) }, - bottomBar = { - BottomNavigation( - backgroundColor = purple200, - modifier = Modifier - .navigationBarsHeight(56.dp) - ) { - tabs.forEach { tab -> - BottomNavigationItem( - icon = { Icon(imageVector = tab.icon, contentDescription = null) }, - label = { Text(text = stringResource(tab.title), color = Color.White) }, - selected = tab == selectedTab, - onClick = { viewModel.selectTab(tab.title) }, - selectedContentColor = LocalContentColor.current, - unselectedContentColor = LocalContentColor.current, - modifier = Modifier.navigationBarsPadding() - ) - } - } + bottomBar = { AnimatedBottomNav( + selectedTab = selectedTab, + onItemClick = {viewModel.selectTab(it)} + ) } +// bottomBar = { +// BottomNavigation( +// backgroundColor = purple200, +// modifier = Modifier +// .navigationBarsHeight(56.dp) +// ) { +// tabs.forEach { tab -> +// BottomNavigationItem( +// icon = { Icon(imageVector = tab.icon, contentDescription = null) }, +// label = { Text(text = stringResource(tab.title), color = Color.White) }, +// selected = tab == selectedTab, +// onClick = { viewModel.selectTab(tab.title) }, +// selectedContentColor = LocalContentColor.current, +// unselectedContentColor = LocalContentColor.current, +// modifier = Modifier.navigationBarsPadding() +// ) +// } +// } +// } ) { innerPadding -> val modifier = Modifier.padding(innerPadding) Crossfade(selectedTab) { destination ->