Сегодня я покажу вам, как создать изогнутое нижнее меню в Jetpack Compose.
Для начала давайте создадим класс, определяющий маршрут (элемент меню).
data class Screen( val route: String, @DrawableRes val icon: Int, @DrawableRes val selectedIcon: Int, )
Я использую icon
и selectedIcon
, потому что в моей реализации они меняются, но вы можете изменить оттенок на любой другой.
Теперь давайте определим bottom bar.
@Composable fun BottomMenuBar( screens: List<Screen>, currentScreen: Screen?, onNavigateTo: (Screen) -> Unit, )
screens
— это маршруты, которые вы хотите отобразить в нижней панели. Вам нужно указать 5 экранов, чтобы эта нижняя панель работала.currentScreen
— это текущий выбранный экран, чтобы мы могли изменить его иконку.onNavigateTo
вызывается при нажатии на иконку в нижней панели.
Прежде чем продолжить, давайте создадим округлую форму, которая будет использоваться в качестве фона.
private fun menuBarShape() = GenericShape { size, _ -> reset() moveTo(0f, 0f) val width = 150f val height = 90f val point1 = 75f val point2 = 85f lineTo(size.width / 2 - width, 0f) cubicTo( size.width / 2 - point1, 0f, size.width / 2 - point2, height, size.width / 2, height ) cubicTo( size.width / 2 + point2, height, size.width / 2 + point1, 0f, size.width / 2 + width, 0f ) lineTo(size.width / 2 + width, 0f) lineTo(size.width, 0f) lineTo(size.width, size.height) lineTo(0f, size.height) close() }
Для получения округлой формы мы используем кубические кривые Безье. Если вы хотите изменить округлую часть, вы можете изменить width
, height
, point1
и point2
. При изменении этих переменных кривизна будет меняться.
Теперь давайте определим предварительный просмотр, чтобы мы могли видеть нижнее меню при его изменении.
@Preview(showSystemUi = true) @Composable private fun Preview() { var currentScreen by remember { mutableStateOf<Screen?>(null) } Box( contentAlignment = Alignment.BottomCenter, modifier = Modifier.fillMaxSize() ) { BottomMenuBar( screens = listOf( Screen( route = "home", icon = R.drawable.outline_home_24, selectedIcon = R.drawable.baseline_home_24, ), Screen( route = "products", icon = R.drawable.outline_collections_24, selectedIcon = R.drawable.baseline_collections_24, ), Screen( route = "cart", icon = R.drawable.outline_shopping_cart_24, selectedIcon = R.drawable.baseline_shopping_cart_24, ), Screen( route = "profile", icon = R.drawable.outline_person_24, selectedIcon = R.drawable.baseline_person_24, ), Screen( route = "chat", icon = R.drawable.outline_chat_24, selectedIcon = R.drawable.baseline_chat_24, ), ), currentScreen = currentScreen, onNavigateTo = { currentScreen = it }, ) } }
Переменная currentScreen
в предварительном просмотре используется для того, чтобы мы могли играть с нижнем меню и видеть, как оно меняется.
Теперь давайте реализуем сам боттом бар.
@Composable fun BottomMenuBar( screens: List<Screen>, currentScreen: Screen?, onNavigateTo: (Screen) -> Unit, ) { val backgroundShape = remember { menuBarShape() } Box { Box( modifier = Modifier .fillMaxWidth() .height(56.dp) .background(Color.White, backgroundShape) .align(Alignment.BottomCenter) ) Column( modifier = Modifier .align(Alignment.TopCenter) ) { FloatingActionButton( shape = RoundedCornerShape(50), containerColor = Color.White, contentColor = Color.Gray, onClick = {}, modifier = Modifier.clip(RoundedCornerShape(50)) ) { Row( modifier = Modifier.size(64.dp) ) { BottomBarItem(screens[2], currentScreen, onNavigateTo) } } Spacer(modifier = Modifier.height(30.dp)) } Row( modifier = Modifier .height(56.dp) .align(Alignment.BottomCenter) ) { BottomBarItem(screens[0], currentScreen, onNavigateTo) BottomBarItem(screens[1], currentScreen, onNavigateTo) Spacer(modifier = Modifier.width(72.dp)) BottomBarItem(screens[3], currentScreen, onNavigateTo) BottomBarItem(screens[4], currentScreen, onNavigateTo) } } }
Нам также необходимо реализовать компонент для пунктов меню,
@Composable private fun RowScope.BottomBarItem( screen: Screen, currentScreen: Screen?, onNavigateTo: (Screen) -> Unit, ) { val selected = currentScreen?.route == screen.route Box( Modifier .selectable( selected = selected, onClick = { onNavigateTo(screen) }, role = Role.Tab, interactionSource = remember { MutableInteractionSource() }, indication = remember { ripple(radius = 32.dp) } ) .fillMaxHeight() .weight(1f), contentAlignment = Alignment.Center ) { BadgedBox( badge = {}, content = { Image( painter = painterResource( id = when { selected -> screen.selectedIcon else -> screen.icon } ), contentDescription = null ) }, ) } }
Ну, вот и все. У вас есть рабочее нижнее меню с изогнутым элементом.