🍹

【Android】Compose で material3の Navigation Barを試す

2024/02/10に公開

はじめに

androidx-navigation-compose を使用してmaterial3のNavigation Barを試してみたいと思います

Compose を使用したナビゲーション  |  Jetpack Compose  |  Android Developers

環境構築や準備

各バージョンや環境

# Android Studio Giraffe | 2022.3.1 Patch 1

$ sw_vers
ProductName: macOS
ProductVersion: 13.4
BuildVersion: 22F66

1. 新規プロジェクト作成

サンプル用のプロジェクトを作成していきます。

「Empty Activity」のTemplateを選択

image1.png

プロジェクト名は「ComposeBottomNavigationExample」としました。

image2.png

※「Kotlin DSL (build.gradle.kts) + Gradle Version Catalogs (Experimental)」を選択してます

2. 必要なパッケージを追加

1-1. gradle/libs.versions.toml に以下を追加

[versions]
androidx-navigation = "2.7.2"

[libraries]
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "androidx-navigation" }

1-2. app/build.gradle に以下を追加

dependencies {
  ...
  implementation(libs.androidx.navigation.compose)
}

compose-navigation2.7.2 だと compileSdk34 にしておく必要があります。

3. ベースとなる MyAppNavHost 関数を追加

新規に MyAppNavHost.kt を以下内容で作成します。

@Composable
fun MyAppNavHost(
    navController: NavHostController = rememberNavController(),
    startDestination: String = "home"
) {
    NavHost(navController = navController, startDestination = startDestination) {
        composable("home") {
            Greeting("Android")
        }
    }
}

4. MainActivityMyAppNavHost を呼ぶように修正

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeBottomNavigationExampleTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    MyAppNavHost() // ← ここを修正
                }
            }
        }
    }
}

ここまでで実行していつもの Greeting の内容が出とけばOKです。

実装

1. シンプルな実装

まずは一番シンプルな実装を試してみたいと思います。

新規に HomeScreen.kt を作成し、以下に修正します。

enum class MainScreenTab(
    val icon: ImageVector,
    val label: String
) {
    Home(
        icon = Icons.Outlined.Home,
        label = "Home"
    ),
    List(
        icon = Icons.Outlined.List,
        label = "List"
    ),
    Settings(
        icon = Icons.Outlined.Info,
        label = "About"
    )
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen() {
    var selectedItem by remember { mutableIntStateOf(0) }
    Scaffold(
        bottomBar = {
            NavigationBar {
                MainScreenTab.values().forEachIndexed { index, item ->
                    NavigationBarItem(
                        icon = { Icon(item.icon, contentDescription = item.label) },
                        label = { Text(item.label) },
                        selected = selectedItem == index,
                        onClick = { selectedItem = index }
                    )
                }
            }
        }
    ) {
        Box(modifier = Modifier.padding(it))
    }
}

@Preview(showBackground = true)
@Composable
fun MainScreenPreview() {
    ComposeBottomNavigationExampleTheme {
        MainScreen()
    }
}

↓Previewでの表示は以下の様になります。

image3.png

2. MainScreenを呼び出すNavGraph を作成

mainScreen用にNavGraphBuilderの拡張関数として定義していきます。

composableではなく navigation を仕様するとネストした NavGraphを定義する事ができます。

private fun NavGraphBuilder.mainScreen() {
    navigation(route = "main", startDestination = "main/entry") {
        composable("main/entry") {
            MainScreen()
        }
    }
}

MyAppNavHost を以下に修正します。

@Composable
fun MyAppNavHost(
    navController: NavHostController = rememberNavController(),
    startDestination: String = "main" // mainに変更
) {
    NavHost(navController = navController, startDestination = startDestination) {
        mainScreen() // 先程の拡張関数 mainScreenを呼び出す
    }
}

ここまでで実際に動かしてみると先程のPreview同様の画面が表示されるかと思います。

3. Navigation Bar内のNavGraphを作成

まず MainScreenTab にNavGraph用の id を追加します。

enum class MainScreenTab(
    val id: String,
    val icon: ImageVector,
    val label: String
) {
    Home(
        id = "main/home",
        icon = Icons.Outlined.Home,
        label = "Home"
    ),
    List(
        id = "main/list",
        icon = Icons.Outlined.List,
        label = "List"
    ),
    Settings(
        id = "main/about",
        icon = Icons.Outlined.Info,
        label = "About"
    )
}

次に MainScreen を以下に修正します。

rememberNavControllerMainScreen のNavigation Bar用のnavControllerを作成し、 currentBackStackEntryAsState (Composable) から現在の選択しているTabを判定します。

@Composable
fun MainScreen() {
    val nestedNavController = rememberNavController()
    val navBackStackEntry by nestedNavController.currentBackStackEntryAsState()
    val currentTab = navBackStackEntry?.destination?.route
    Scaffold(
        bottomBar = {
            NavigationBar {
                MainScreenTab.values().forEachIndexed { index, item ->
                    NavigationBarItem(
                        icon = { Icon(item.icon, contentDescription = item.label) },
                        label = { Text(item.label) },
                        selected = currentTab == item.id,
                        onClick = { nestedNavController.navigate(item.id) }
                    )
                }
            }
        }
    ) {
        Box(modifier = Modifier.padding(it)) {
            NavHost(
                navController = nestedNavController,
                startDestination = "main/home",
                modifier = Modifier,
            ) {
                composable("main/home") {
                    Text("main/home")
                }
                composable("main/list") {
                    Text("main/list")
                }
                composable("main/about") {
                    Text("main/about")
                }
            }
        }
    }
}

image4.gif

この記事は以下の情報を参考にして執筆しました

Discussion