Compose Multiplatform 実践:CMPでのNavigationBarボトムバーの実装

はじめに

Compose Multiplatform (略称CMP)

今日はCMPのNavigationBarボトムバーを実装します
これはmaterial 3で提供されているcomposableコンポーネントで
ユーザーがアプリでよく使用するページ切替用のボトムバーを作成できます

実際に作成すると次のようになります

https://ithelp.ithome.com.tw/upload/images/20240812/201683355J8smYXCg7.png

NavigationBarボトムバーの実装

今日はCMPでNavigationBarボトムバーを実装する方法を段階的に紹介します
ボトムバーの構造を定義し
スタイル動作を追加する必要があります

@Composable
fun BottomNavigation(navController: NavController) {
    val screens = listOf(
        Triple("🏠", "Lessons", ElegantJapaneseScreen.Learning.name),
        Triple("あ", "Ad", ElegantJapaneseScreen.Ad.name),
        Triple("🔍", "Grammar", ElegantJapaneseScreen.Grammar.name),
        Triple("👤", "Settings", ElegantJapaneseScreen.Setting.name)
    )

    NavigationBar(
        modifier = Modifier.height(60.dp),
        containerColor = MaterialTheme.colorScheme.surface,
    ) {
        val navBackStackEntry by navController.currentBackStackEntryAsState()
        val currentDestination = navBackStackEntry?.destination

        screens.forEach { (icon, label, route) ->
            NavigationBarItem(
                icon = { Text(icon) },
                label = { if (label.isNotEmpty()) Text(label) },
                selected = currentDestination?.route == route,
                colors = NavigationBarItemDefaults.colors(
                    selectedIconColor = Color.Blue,
                    selectedTextColor = Color.Blue,
                    indicatorColor = Color.Blue.copy(alpha = 0.5f),
                    unselectedIconColor = Color.Gray,
                    unselectedTextColor = Color.Gray
                ),
                onClick = {
                    if (currentDestination?.route != route) {
                        navController.navigate(route) {
                            navController.graph.findStartDestination().route?.let {
                                popUpTo(it) {
                                    saveState = true
                                }
                            }
                            launchSingleTop = true
                            restoreState = true
                        }
                    }
                }
            )
        }
    }
}

主要コードの説明

  1. リストscreensを定義しました:ここでのTriple
    3つのパラメータを入れられるコンテナです
    ここでカスタマイズした内容を通じて
    異なるNavigationBarItemを生成します
  2. NavigationBar( modifier = Modifier.height(60.dp), containerColor = MaterialTheme.colorScheme.surface, ) {....}: これも多くのcomposeの基本的な書き方で
    NavigationBarでNavigationBarItemをラップすると
    高さ60 dpのボトムバーが得られます

  3. navController: NavControllerを渡すことを想定しています:
    これは数日前に画面ナビゲーションに使用したコントローラーです
    (忘れた場合は前回の記事を参照してください)

  4. val navBackStackEntry by navController.currentBackStackEntryAsState()
    このコードはKotlinのby構文を使用してnavBackStackEntry変数を作成し
    navController.currentBackStackEntryAsState()の戻り値に委譲しています

  5. val currentDestination = navBackStackEntry?.destination
    この行はnavBackStackEntryから現在の目的地(currentDestination)を抽出します

  6. 上記の4~5は、主に現在のナビゲーション先を取得するためのもので
    ここでロジックに基づいてUI状態の更新を処理できるようにします

  7. selected = currentDestination?.route == route
    この行を追加した主な理由は、同じ画面で同じBottomBarItemをクリックする問題を防ぐためです

  8. NavigationBarItemのパラメータは、以前の他のcomposableと同様に
    開発者のシナリオに応じて内容を調整できます

実際の使用例

以前のCompose Navigationの外側にScaffoldをラップしたことを覚えていますか?
ここで
ScaffoldbottomBar
実装したばかりのBottomNavigationを追加できます

@Composable
fun ElegantAccessApp(
    vm: MainViewModel,
    navController: NavHostController = rememberNavController(),
) {
    vm.navController = navController

    navController.addOnDestinationChangedListener { _, destination, _ ->
        println("Navigation: Navigated to ${destination.route}")
    }

    Scaffold(
        // ここに追加
        bottomBar = {
           BottomNavigation(navController)
        },
    ) { paddingValues ->
        NavHost(
            navController = navController,
            startDestination = ElegantJapaneseScreen.Learning.name,
            modifier = Modifier
                .padding(paddingValues)
                .safeDrawingPadding()
                .fillMaxSize()
        ) {
            routeLearningScreen(navController)
            routeAdScreen(navController)
            routeAScreen(navController)
            routeBScreen(navController)
            routeCScreen(navController)
            routeSettingScreen(navController)
        }

    }
}
一部のページでNavigationBarを表示したくない場合はどうすればいいですか?

この場合
shouldShowBottomBarという関数を作成して
現在NavigationBarを表示すべきかどうかを判断できます

方法はとても簡単です

NavigationBarを表示したいrouteのリストを作成し
現在のrouteと比較します

ここで前に定義したenumが威力を発揮します
定義済みのenumを通じて
対応するページのRouteを見つけて追加するだけです

@Composable
fun shouldShowBottomBar(navController: NavHostController): Boolean {
    val currentRoute = navController.currentBackStackEntryAsState().value?.destination?.route
    return currentRoute in listOf(
        ElegantJapaneseScreen.Learning.name,
        ElegantJapaneseScreen.Ad.name,
        ElegantJapaneseScreen.Contest.name,
        ElegantJapaneseScreen.Grammar.name,
        ElegantJapaneseScreen.About.name,
        ElegantJapaneseScreen.Setting.name
    )
}

次にScaffoldif判定を追加するだけです

Scaffold(
    bottomBar = {
        if (shouldShowBottomBar(navController)) {
            BottomNavigation(navController)
        }
    },
)

You might also enjoy