Search code examples
androidkotlinandroid-jetpack-composeandroid-jetpack-compose-material3bottom-navigation-bar

Android Navigation Bar Item is clipped by Navigation Bar


I'm using a bottom navigation bar with 5 items and I want that 3' icon in the middle is drawn over the top of the bottom navigation bar.

This is how my code looks like

@Composable
private fun BottomNavigationBar() {
    var selectedItem by remember { mutableIntStateOf(0) }
    NavigationBar(
        modifier = Modifier
            .fillMaxWidth()
            .height(96.dp)
            .padding(top = 26.dp)
            .border(
                width = 1.dp,
                color = Color(0xFFE5E5E5),
            ),
        containerColor = Color.White,
    ) {
        navigationItems.forEachIndexed { index, item ->
            if (item is NavigationItem.Guide) {
                NavigationBarItem(
                    modifier = Modifier
                        .width(74.dp)
                        .height(82.dp)
                        .offset(y = (-20).dp),
                    selected = selectedItem == index,
                    onClick = { selectedItem = index },
                    icon = {
                        Image(
                            modifier = Modifier
                                .size(60.dp),
                            painter = painterResource(id = R.drawable.ic_nav_guide),
                            contentDescription = ""
                        )
                    },
                    label = { Text(text = item.title ?: "") }
                )
            } else {
                NavigationBarItem(
                    selected = selectedItem == index,
                    onClick = { selectedItem = index },
                    icon = {
                        Icon(
                            painter = painterResource(id = item.icon ?: 0),
                            contentDescription = "Nav Icon"
                        )
                    },
                    label = { Text(text = item.title ?: "") }
                )
            }
        }
    }
}

enter image description here

and this his how it actualy looks like.

How can I fix the clipping of the image, so that one half is displayed over and the other half inside the nav bar?


Solution

  • You will have to create your own MyNavigationBar Composable and use the clip Modifier to cut the Shape from the NavigationBar.

    You can use the following shape class:

    private val HALF_CIRCLE_RADIUS_DP = 32.dp  // specify radius of circle in dp here
    
    class BottomCurve : Shape {
    
        override fun createOutline(
            size: Size,
            layoutDirection: LayoutDirection,
            density: Density
        ): Outline {
    
            // convert to px
            val halfCircleRadius = with(density) { HALF_CIRCLE_RADIUS_DP.toPx() }
    
            return Outline.Generic(path = Path().apply {
                val halfCircleCenterX = size.width / 2
                val halfCircleCenterY = halfCircleRadius.toFloat()
    
                // Move to the center of the half-circle and start drawing the semicircle
                moveTo(halfCircleCenterX, halfCircleCenterY)
    
                // Draw the half circle on the top line of the rectangle
                arcTo(
                    rect = Rect(
                        left = halfCircleCenterX - halfCircleRadius,
                        top = 0f,
                        right = halfCircleCenterX + halfCircleRadius,
                        bottom = (halfCircleRadius * 2).toFloat()
                    ),
                    startAngleDegrees = 180f,
                    sweepAngleDegrees = 180f,
                    forceMoveTo = true
                )
    
                lineTo(size.width, halfCircleRadius.toFloat())
                lineTo(size.width, size.height)
                lineTo(0f, size.height)
                lineTo(0f, halfCircleRadius.toFloat())
    
            })
        }
    }
    

    Then, create your MyNavigationBar Composable:

    @Composable
    fun MyNavigationBar(
        modifier: Modifier = Modifier,
        containerColor: Color = NavigationBarDefaults.containerColor,
        contentColor: Color = MaterialTheme.colorScheme.contentColorFor(containerColor),
        tonalElevation: Dp = NavigationBarDefaults.Elevation,
        windowInsets: WindowInsets = NavigationBarDefaults.windowInsets,
        content: @Composable RowScope.() -> Unit
    ) {
        Surface(
            color = containerColor,
            contentColor = contentColor,
            tonalElevation = tonalElevation,
            modifier = modifier
                .height(118.dp)
                .clip(BottomCurve())
        ) {
            Row(
                modifier = Modifier
                    .fillMaxSize()
                    .windowInsetsPadding(windowInsets)
                    .selectableGroup(),
                horizontalArrangement = Arrangement.spacedBy(8.dp),
                verticalAlignment = Alignment.Bottom,
                content = content
            )
        }
    }
    

    Finally, invoke it as follows:

    var selectedItem by remember { mutableIntStateOf(0) }
    MyNavigationBar(
        containerColor = Color.White,
    ) {
        //..
    }
    

    The output looks as follows:

    enter image description here

    You can adjust the Path or modify the offset as per your needs.