【正社員】還元率83%【フリーランス】マージン一律5万円で案件をご紹介させていただきます。 詳細はこちら

【Android】<Kotlin>Navigation Compose × BottomNavigationを使ったタブUIを実装する方法について解説

【Android】<Kotlin>Navigation-Compose × BottomNavigationを使ったタブUIを実装する方法について解説
すだ

みなさまこんにちは〜!
メモリアインクのすだです。

本日は、
KotlinのNavigation ComposeとBottomNavigationを使って
複数の画面を切り替える方法について解説していきます!

この記事を読んでわかること…
・Navigation ComposeとBottomNavigationを使って画面下部にナビゲーションバーを実装する方法

Navigation Composeに関しては以下の記事でまとめています。
ぜひ併せてご覧ください。

目次

環境

  • Kotlin (ver 1.9.0)
  • Android Studio (Giraffe | 2022.3.1 Patch 3)

BottomNavigationとは?

BottomNavigationJetpack Compose に標準で用意されている UI コンポーネントのひとつで、
画面の下にナビゲーションバー(タブ)を配置するためのパーツ」です。

公式では androidx.compose.material.BottomNavigation として提供されています。

基本的には、

Scaffold(
    bottomBar = {
        BottomNavigation {
            // 各タブのアイテムを配置
        }
    }
) {
    // 中身(NavHostなど)
}

上記のように構成します。

Scaffold で画面全体の骨組みを作り、その中で bottomBarBottomNavigation を設置するのがComposeの基本スタイルです。

イメージとしては以下の図のような画面です。

+---------------------------+
|        画面コンテンツ       |
|                           |
|                           |
|                           |
+---------------------------+
| ホーム | 登録 | 設定 | メニュー | ← これが BottomNavigation
+---------------------------+

BottomNavigationとの連携方法

BottomNavigationを使うには、Navigation ComposのNavControllerと連携して画面ごとのルートに遷移させるようにします。

基本構成は以下です:

  1. BottomNavigationItem をループで表示
  2. navController.navigate(route) で画面遷移
  3. NavHost と現在の画面を紐付け

今回は例として
・画面下にタブ(BottomNavigation)を設置し、
・「ホーム画面」と「設定画面」を切り替えられるようにする
・さらには、タブの選択状態や画面遷移の状態も適切に管理する
という実装を行います。

まずは全体のコードから▼

sealed class Screen(val route: String, val label: String, val icon: ImageVector) {
    object Home : Screen("home", "ホーム", Icons.Default.Home)
    object Settings : Screen("settings", "設定", Icons.Default.Settings)
}

val bottomScreens = listOf(
    Screen.Home,
    Screen.Settings
)

@Composable
fun MainScreen() {
    val navController = rememberNavController()
    Scaffold(
        bottomBar = {
            BottomNavigation {
                val currentRoute = navController.currentBackStackEntryAsState().value?.destination?.route
                bottomScreens.forEach { screen ->
                    BottomNavigationItem(
                        icon = { Icon(screen.icon, contentDescription = screen.label) },
                        label = { Text(screen.label) },
                        selected = currentRoute == screen.route,
                        onClick = {
                            if (currentRoute != screen.route) {
                                navController.navigate(screen.route) {
                                    popUpTo(navController.graph.startDestinationId) { saveState = true }
                                    launchSingleTop = true
                                    restoreState = true
                                }
                            }
                        }
                    )
                }
            }
        }
    ) { innerPadding ->
        NavHost(
            navController = navController,
            startDestination = Screen.Home.route,
            modifier = Modifier.padding(innerPadding)
        ) {
            composable(Screen.Home.route) { HomeScreen() }
            composable(Screen.Settings.route) { SettingsScreen() }
        }
    }
}

@Composable
fun HomeScreen() {
    Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
        Text("ホーム画面")
    }
}

@Composable
fun SettingsScreen() {
    Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
        Text("設定画面")
    }
}

少しずつ区切って解説していきます。

① sealed class Screen(画面の定義)

sealed class Screen(val route: String, val label: String, val icon: ImageVector) {
    object Home : Screen("home", "ホーム", Icons.Default.Home)
    object Settings : Screen("settings", "設定", Icons.Default.Settings)
}

画面ごとのルート名("home" など)・表示名・アイコンをまとめて定義。
sealed class を使うことで、BottomBarの画面一覧をタイプセーフに管理できます。

② Scaffold(ボトムバーを一括管理)

Scaffold(
    bottomBar = {

Scaffold は トップバーやボトムバーなどを一括管理する便利なレイアウトコンポーネントです。
画面本体(NavHost など)が「バーの下に隠れないようにする」ために、余白(padding)を自動で計算してくれます。

Composeでは、NavHost 自体が UI上に表示される領域(画面本体)を担うため、

Scaffold(
    topBar = { TopAppBar(...) },
    bottomBar = { BottomNavigation(...) }
) { innerPadding ->
    NavHost(..., modifier = Modifier.padding(innerPadding))
}

このようにinnerPaddingをセットしてBottomNavigation(下部バー)と中身が重なるのを防ぎます。

③ bottomScreens(ボトムバーに表示する画面リスト)

val bottomScreens = listOf(
    Screen.Home,
    Screen.Settings
)

ここに定義した画面だけが BottomNavigation に表示されます。

④ MainScreen() の構成(UI全体)

@Composable
fun MainScreen() {
    val navController = rememberNavController()

画面構成のメインとなる Composableです。NavController を使って画面遷移を管理します。

⑤ BottomNavigation(下部タブUI)

BottomNavigation {
    val currentRoute = navController.currentBackStackEntryAsState().value?.destination?.route

navController. ~ で現在表示中の画面のルート(文字列)を取得します。
これで「どの画面が表示中か?」を判断してタブの選択状態を制御します。

⑥ BottomNavigationItem(タブの各項目)

bottomScreens.forEach { screen ->
    BottomNavigationItem(
        icon = { Icon(screen.icon, contentDescription = screen.label) },
        label = { Text(screen.label) },
        selected = currentRoute == screen.route,
        onClick = {
            if (currentRoute != screen.route) {
                navController.navigate(screen.route) {
                    popUpTo(navController.graph.startDestinationId) { saveState = true }
                    launchSingleTop = true
                    restoreState = true
                }
            }
        }
    )
}

先ほど定義したbottomScreensをループし、BottomNavigationItemを使って各項目を表示させます。
各パラメータは以下を意味しています。

icon:アイコン表示(Icons.Default.Home など)
label:テキスト表示
selected:選択中かどうか
onClick:タップ時の画面遷移処理

まとめ

おつかれさまでした。いかがでしたでしょうか!

Navigation ComposeとBottomNavigationを組み合わせれば、タブUIをComposeだけでスッキリと実装できます。
画面遷移はNavControllerに任せる、
画面表示はNavHostで管理、
そしてUIの下部はBottomNavigationで実装 といったイメージで実践してみてください!

すだ

技術者としてのキャリアパスを次のレベルへと進めたい皆様、
未経験からIT・Webエンジニアを目指すなら【ユニゾンキャリア】
を通じて、
自分の市場価値をさらに向上させてみませんか?

それではまた次の記事でお会いしましょう!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

弊社テックブログをご愛読いただきありがとうございます。
当テックブログを運用している株式会社メモリアインクは、
【正社員】還元率83%
【フリーランス】マージン一律5万円で案件のご紹介
と、エンジニアの皆様に分かりやすい形で稼げる仕組みを構築し提供させていただいております。

コメント

コメントする

目次