Reputation: 1290
I'm trying to build out my Bottom navigation like this:
@Composable
fun BottomNavBar(navController: NavController) {
Column(
Modifier.background(colorResource(id = R.color.pastel_orange_white))
) {
BottomNavigation(
modifier = Modifier
.defaultMinSize(minHeight = 70.dp),
backgroundColor = colorResource(id = R.color.bottom_nav_dark)
) {
val navItems = arrayOf(
BottomNavItem(
stringResource(id = R.string.title_home),
R.drawable.ic_home,
Screen.Home.route
),
BottomNavItem(
stringResource(id = R.string.subjects_title),
R.drawable.ic_subject,
Screen.Subjects.route
),
BottomNavItem(
stringResource(id = R.string.grades_title),
R.drawable.ic_grade,
Screen.Grades.route
),
BottomNavItem(
"H/W",
R.drawable.ic_assignments,
Screen.Assignments.route
)
)
// observe the backstack
val navBackStackEntry by navController.currentBackStackEntryAsState()
// observe current route to change the icon
// color,label color when navigated
val currentRoute = navBackStackEntry?.destination?.route
navItems.forEach { navItem ->
BottomNavigationItem(
selected = currentRoute == navItem.route,
onClick = {
navController.navigate(navItem.route)
},
icon = {
Box(
Modifier
.width(70.dp)
.height(30.dp)
.background(
colorResource(id = if (currentRoute == navItem.route) R.color.bottom_nav_light else R.color.bottom_nav_dark),
RoundedCornerShape(32.dp)
),
contentAlignment = Alignment.Center
) {
Icon(
painter = painterResource(id = navItem.icon),
contentDescription = navItem.label,
tint = colorResource(id = R.color.black)
)
}
},
label = {
Text(text = navItem.label, fontSize = 14.sp)
},
alwaysShowLabel = true,
selectedContentColor = colorResource(id = R.color.black),
unselectedContentColor = colorResource(id = R.color.black)
)
}
}
}
}
I need to add some extra space between the label and the icon parts, since I'm applying a small background color to the selected item. I tried with paddings, column arrangements, etc. but didn't find something that actually affected the spacing. Any pointers?
Upvotes: 3
Views: 3018
Reputation: 1041
One simple solution would be using Modifier.offset
to y
axis. Like this:
...
icon = {
Icon(
modifier = Modifier.offset(y = (-4).dp),
painter = painterResource(icon),
contentDescription = stringResource(description),
)
}
...
In my case -4.dp
worked well, but you may adjust it for your needs.
Upvotes: 0
Reputation: 420
Add padding to your icon
items.forEach { item ->
BottomNavigationItem(
icon = {
Icon(
modifier=Modifier.padding(6.dp), //add this line
painter = painterResource(id = item.icon),
tint = MaterialTheme.colorScheme.onSurface,
contentDescription = stringResource(id = item.title)
)
},
label = {
Text(
text = stringResource(id = item.title),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurface
)
},
selected = currentRoute == item.route,
)
}
Upvotes: 3
Reputation: 499
I managed to add padding between the icon and the label. In fact, when I was trying this initially, I was applying the padding to the text/label and not the icon, but if we apply padding to the icon, it is possible to apply that spacing. Just add a Modifier.padding(bottom = YOURDP)
to your Icon and it will work.
I think this is possible because, after analyzing the Material Design code to apply the Label with Icon, it applies the Label relative to Icon (you can see that on the method below), considering the existing space, so if we increase the space that Icon occupies, it will so that the label is lower.
/**
* Places the provided [labelPlaceable] and [iconPlaceable] in the correct position, depending on
* [iconPositionAnimationProgress].
*
* When [iconPositionAnimationProgress] is 0, [iconPlaceable] will be placed in the center, as with
* [placeIcon], and [labelPlaceable] will not be shown.
*
* When [iconPositionAnimationProgress] is 1, [iconPlaceable] will be placed near the top of item,
* and [labelPlaceable] will be placed at the bottom of the item, according to the spec.
*
* When [iconPositionAnimationProgress] is animating between these values, [iconPlaceable] will be
* placed at an interpolated position between its centered position and final resting position.
*
* @param labelPlaceable text label placeable inside this item
* @param iconPlaceable icon placeable inside this item
* @param constraints constraints of the item
* @param iconPositionAnimationProgress the progress of the icon position animation, where 0
* represents centered icon and no label, and 1 represents top aligned icon with label.
* Values between 0 and 1 interpolate the icon position so we can smoothly move the icon.
*/
private fun MeasureScope.placeLabelAndIcon(
labelPlaceable: Placeable,
iconPlaceable: Placeable,
constraints: Constraints,
/*@FloatRange(from = 0.0, to = 1.0)*/
iconPositionAnimationProgress: Float
): MeasureResult {
val height = constraints.maxHeight
// TODO: consider multiple lines of text here, not really supported by spec but we should
// have a better strategy than overlapping the icon and label
val baseline = labelPlaceable[LastBaseline]
val baselineOffset = CombinedItemTextBaseline.roundToPx()
// Label should be [baselineOffset] from the bottom
val labelY = height - baseline - baselineOffset
val unselectedIconY = (height - iconPlaceable.height) / 2
// Icon should be [baselineOffset] from the text baseline, which is itself
// [baselineOffset] from the bottom
val selectedIconY = height - (baselineOffset * 2) - iconPlaceable.height
val containerWidth = max(labelPlaceable.width, iconPlaceable.width)
val labelX = (containerWidth - labelPlaceable.width) / 2
val iconX = (containerWidth - iconPlaceable.width) / 2
// How far the icon needs to move between unselected and selected states
val iconDistance = unselectedIconY - selectedIconY
// When selected the icon is above the unselected position, so we will animate moving
// downwards from the selected state, so when progress is 1, the total distance is 0, and we
// are at the selected state.
val offset = (iconDistance * (1 - iconPositionAnimationProgress)).roundToInt()
return layout(containerWidth, height) {
if (iconPositionAnimationProgress != 0f) {
labelPlaceable.placeRelative(labelX, labelY + offset)
}
iconPlaceable.placeRelative(iconX, selectedIconY + offset)
}
}
Upvotes: 0
Reputation: 24024
I don't think you can do it using a BottomNavigationItem
. The Material Design Library for Compose follows the Material Design Specification as you can see here.
But you don't need to use a BottomNavigationItem
to display the items in your BottomAppBar
. You can use any composable, like below:
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.weight(1f)
.clickable {
navController.navigate(navItem.route)
}) {
val color =
if (currentRoute == navItem.route)
LocalContentColor.current
else
LocalContentColor.current.copy(alpha = ContentAlpha.medium)
Icon(
painter = painterResource(id = navItem.icon),
navItem.label,
tint = color
)
// Set the space that you want...
Spacer(modifier = Modifier.height(4.dp))
Text(text = navItem.label, color = color, fontSize = 12.sp)
}
However, as I mentioned above, the library follows the Material Design specs, which define the BottomAppBar
's height as 56dp. Therefore, if you want something more custom, you need to it by your own (using a Row
, for instance).
Upvotes: 4