Compose Multiplatform 実践:CMPでのNavigationBarボトムバーの実装
はじめに
Compose Multiplatform (略称CMP)
今日はCMP
のNavigationBarボトムバーを実装します
これはmaterial 3で提供されているcomposableコンポーネントで
ユーザーがアプリでよく使用するページ切替用のボトムバーを作成できます
実際に作成すると次のようになります
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
}
}
}
)
}
}
}
主要コードの説明
:
-
リスト
screens
を定義しました:ここでのTriple
は3つのパラメータを入れられるコンテナです
ここでカスタマイズした内容を通じて
異なる
NavigationBarItem
を生成します -
NavigationBar( modifier = Modifier.height(60.dp), containerColor = MaterialTheme.colorScheme.surface, ) {....}
: これも多くのcomposeの基本的な書き方でNavigationBarで
NavigationBarItem
をラップすると高さ60 dpのボトムバーが得られます
-
navController: NavController
を渡すことを想定しています:これは数日前に画面ナビゲーションに使用したコントローラーです
(忘れた場合は前回の記事を参照してください)
-
val navBackStackEntry by navController.currentBackStackEntryAsState()
:このコードはKotlinの
by
構文を使用してnavBackStackEntry
変数を作成しnavController.currentBackStackEntryAsState()の戻り値に委譲しています
-
val currentDestination = navBackStackEntry?.destination
:この行は
navBackStackEntry
から現在の目的地(currentDestination)を抽出します -
上記の
4~5
は、主に現在のナビゲーション先を取得するためのものでここでロジックに基づいてUI状態の更新を処理できるようにします
-
selected = currentDestination?.route == route
:この行を追加した主な理由は、同じ画面で同じBottomBarItemをクリックする問題を防ぐためです
-
NavigationBarItemのパラメータは、以前の他のcomposableと同様に
開発者のシナリオに応じて内容を調整できます
以前のCompose Navigation
の外側にScaffold
をラップしたことを覚えていますか?
ここで
Scaffold
のbottomBar
に
実装したばかりの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)
}
}
}
この場合
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
)
}
次にScaffold
にif判定
を追加するだけです
Scaffold(
bottomBar = {
if (shouldShowBottomBar(navController)) {
BottomNavigation(navController)
}
},
)