androidandroid-jetpack-composematerial-components-androidjetpack-compose-navigationandroid-jetpack-compose-scaffold

How to change icon if selected and unselected in android jetpack compose for NavigationBar like selector we use in xml for selected state?


I want to use outlined and filled icons based on selected state in NavigationBar just like google maps app, using jetpack compose. In case of xml we use selector so what do we use for compose ?

Here is my code ->

MainActivity.kt

@ExperimentalMaterial3Api
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Handle the splash screen transition.
        installSplashScreen()
        setContent {
            MyApp()
        }
    }
}

@ExperimentalMaterial3Api
@Composable
fun MyApp() {
    MyTheme {
        val items = listOf(
            Screen.HomeScreen,
            Screen.MusicScreen,
            Screen.ProfileScreen
        )
        val navController = rememberNavController()
        Scaffold(
            bottomBar = {
                NavigationBar {
                    val navBackStackEntry by navController.currentBackStackEntryAsState()
                    val currentDestination = navBackStackEntry?.destination
                    items.forEach { screen ->
                        NavigationBarItem(
                            icon = {
                                Icon(
                                    screen.icon_outlined,
                                    contentDescription = screen.label.toString()
                                )
                            },
                            label = { Text(stringResource(screen.label)) },
                            selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
                            onClick = {
                                navController.navigate(screen.route) {
                                    // Pop up to the start destination of the graph to
                                    // avoid building up a large stack of destinations
                                    // on the back stack as users select items
                                    popUpTo(navController.graph.findStartDestination().id) {
                                        saveState = true
                                    }
                                    // Avoid multiple copies of the same destination when
                                    // reselecting the same item
                                    launchSingleTop = true
                                    // Restore state when reselecting a previously selected item
                                    restoreState = true
                                }
                            }
                        )
                    }
                }
            }
        ) { innerPadding ->
            NavHost(
                navController,
                startDestination = Screen.HomeScreen.route,
                Modifier.padding(innerPadding)
            ) {
                composable(route = Screen.HomeScreen.route) {
                    HomeScreen()
                }
                composable(route = Screen.MusicScreen.route) {
                    MusicScreen()
                }
                composable(route = Screen.ProfileScreen.route) {
                    ProfileScreen()
                }
            }
        }
    }
}

@ExperimentalMaterial3Api
@Preview(
    showBackground = true, name = "Light mode",
    uiMode = Configuration.UI_MODE_NIGHT_NO or Configuration.UI_MODE_TYPE_NORMAL
)
@Preview(
    showBackground = true, name = "Night mode",
    uiMode = Configuration.UI_MODE_NIGHT_YES or Configuration.UI_MODE_TYPE_NORMAL
)
@Composable
fun DefaultPreview() {
    MyApp()
}

Screen.kt

sealed class Screen(
    val route: String,
    @StringRes val label: Int,
    val icon_outlined: ImageVector,
    val icon_filled: ImageVector
) {
    object HomeScreen : Screen(
        route = "home_screen",
        label = R.string.home,
        icon_outlined = Icons.Outlined.Home,
        icon_filled = Icons.Filled.Home
    )

    object MusicScreen : Screen(
        route = "music_screen",
        label = R.string.music,
        icon_outlined = Icons.Outlined.LibraryMusic,
        icon_filled = Icons.Filled.LibraryMusic,
    )

    object ProfileScreen : Screen(
        route = "profile_screen",
        label = R.string.profile,
        icon_outlined = Icons.Outlined.AccountCircle,
        icon_filled = Icons.Filled.AccountCircle,
    )
}

HomeScreen.kt

@Composable
fun HomeScreen() {
    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center,
    ) {
        Surface(color = MaterialTheme.colorScheme.background) {
            Text(
                text = "Home",
                color = Color.Red,
                fontSize = MaterialTheme.typography.displayLarge.fontSize,
                fontWeight = FontWeight.Bold
            )
        }
    }
}

@Preview(
    showBackground = true, name = "Light mode",
    uiMode = Configuration.UI_MODE_NIGHT_NO or Configuration.UI_MODE_TYPE_NORMAL
)
@Preview(
    showBackground = true, name = "Night mode",
    uiMode = Configuration.UI_MODE_NIGHT_YES or Configuration.UI_MODE_TYPE_NORMAL
)
@Composable
fun HomeScreenPreview() {
    HomeScreen()
}

Do I still need to use selector xml or there is alternative way in jetpack compose?


Solution

  • Yeah, you just make a simple if statement based on selected state, like this:

    items.forEach { screen ->
        val selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true
        NavigationBarItem(
            icon = {
                Icon(
                    if (selected) screen.icon_filled else screen.icon_outlined,
                    contentDescription = screen.label.toString()
                )
            },
            selected = selected,
        )
    }