Compose Multiplatform 実践:CMPでのCompose Navigationによる画面遷移の実装
Compose Multiplatform (略称CMP)
今日はCMP
での画面遷移機能を実装します
以前、単一画面アプリ
がまだ一般的でなかった頃
開発者は新しい画面を表示するために新しいActivityを次々と作成していました
しかし、Activityがスタック内で次々と追加されるため
パフォーマンスの問題を考慮する必要がありました。
時間が経つにつれ 従来のレイアウト時代にNavigation-graphが導入され これによって画面ナビゲーションの管理が簡素化されました
そして現在、Compose
も同様の概念を導入し
Compose Navigation
と呼ばれています
もちろんこの概念はCMP
にも適用できるので
今日はこれを実装してみましょう
目錄
- Compose Multiplatform 實戰:放輕鬆點,初探CMP
- Compose Multiplatform 實戰:初戰,安裝CMP環境吧
- Compose Multiplatform 實戰:續戰,用Wizard創建CMP專案
- Compose Multiplatform 實戰:在Android、iOS模擬器上跑CMP專案
- Compose Multiplatform 實戰:CMP的專案結構理解與編譯配置
- Compose Multiplatform 實戰:CMP中跨平台Android、iOS程式碼的進入點
- Compose Multiplatform 實戰:在CMP的Compose中用Material Design3 Theme
- Compose Multiplatform 實戰:CMP用Compose實作跨平台畫面
- Compose Multiplatform 實戰:使用 expect 和 actual 實現跨平台程式碼
- Compose Multiplatform 實戰:CMP中實作Compose Navigation頁面切換
- Compose Multiplatform 實戰:CMP中透過StateFlow來管理UI狀態
- Compose Multiplatform 實戰:CMP中實作NavigationBar底部欄
- Compose Multiplatform 實戰:CMP中使用koin來依賴注入Dependency Injection
- Compose Multiplatform 實戰:CMP實作跨平台資料庫SqlDelight
- Compose Multiplatform 實戰:CMP中使用ROOM開發跨平台資料庫 & 疑難雜症
- 以下の例のように追加し、完了後
sync gradle
します ```toml [versions] navigation-compose = “2.7.0-alpha07”
[libraries] navigation-compose = { module = “org.jetbrains.androidx.navigation:navigation-compose”, version.ref = “navigation-compose” }
<div class="c-border-content-title-1">ライブラリをbuild.gradle.ktsにインポート</div>
* 今回も共通部分なので、`commonMain`に以下を追加します:
```kotlin
sourceSets {
commonMain.dependencies {
implementation(libs.navigation.compose)
}
}
- 以前のコードエントリーポイント
App()
を覚えていますか? 今回はElegantAccessApp()
という関数を作成し(好きな名前で構いません) そこに配置します
@Composable
@Preview
fun App() {
ElegantAccessComposeTheme {
ElegantAccessApp()
}
}
@Composable
fun ElegantAccessApp(
navController: NavHostController = rememberNavController(),
) {
// .. TODO Compose Navigation
}
ElegantAccessApp()
の実装
@Composable
fun ElegantAccessApp(
navController: NavHostController = rememberNavController(),
) {
navController.addOnDestinationChangedListener { _, destination, _ ->
println("Navigation: Navigated to ${destination.route}")
}
Scaffold { 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)
}
}
}
主要コードの説明
:
-
パラメータ
navController: NavHostController
:NavHostController
を受け取ります 提供されない場合はデフォルトで新しく作成します これにより将来的により外側の層からnavController
を渡せるように柔軟性を持たせています このような書き方が不要な場合は 関数内で直接インスタンスを作成することもできます -
navController.addOnDestinationChangedListener
:Navigation画面が遷移する際に リスナーとして機能します。最初に慣れていない場合は ここにlog
を追加して現在のパスを表示すると便利です -
Scaffold
:Material Design3の基本的な視覚的レイアウト構造を提供するレイアウト構造です 例えば、よく使われるtopbar
(トップバー)、bottomBar
(ボトムバー)などを設定できます 自分で実装したComposeコンポーネントを入力できるようにし 後で拡張したい場合もここから始めることができます 詳細は下図を参照してください:
実際に遷移したい画面はScaffold(){ //Here }
内の関数型に配置します
ここでpaddingValues
は
他のコンポーネント(例:topbar
)を設定した場合
それに対応する値を返し
コンテンツが隠れるのを防ぐことができます
navController
は後で画面遷移に使用するインスタンスですNavHost( navController = navController, startDestination = ElegantJapaneseScreen.Learning.name, modifier = Modifier .padding(paddingValues) .safeDrawingPadding() .fillMaxSize() ) { // ... Router }
ここでは前述のnavController
を入力し NavHostと紐付けますstartDestination
は開始画面のString
で 自由に定義できますmodifier
:前述のpaddingValues
を設定し 画面が隠れるのを防ぎます NavHost内の関数型では 遷移先画面の定義を実装します
- 目標画面のRouterの実装
各画面の名前を定義する必要があるため
ここではenum
を使用して
各画面を表現します
enum class ElegantJapaneseScreen {
Main,
Learning,
Contest,
Grammar,
About,
Setting,
Ad,
}
次に、いくつかの関数を実装します
例:routeAScreen
、routeBScreen
、routeCScreen
など
NavHost
内では
composable(ElegantJapaneseScreen.Contest.name)
を通じて
具体的なComposable関数を定義できます
ここではKotlinのextension(拡張)の概念を使用して
NavGraphBuilder
を拡張し
直感的にNavHost
の関数型内に配置できるようにします
fun NavGraphBuilder.routeAScreen(
navController: NavHostController,
) {
composable(ElegantJapaneseScreen.Contest.name) {
AScreen(navController)
}
}
そして これをNavHost内に配置するだけで 使用できるようになります
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)
}
最後に
最初に作成したnavController
を覚えていますか?
同じnavControllerを使用しているのがわかります
上記のすべてのステップを完了したら
navController
を操作するだけで
NavHost内で定義されたページにナビゲートできます
例:
navController.navigate(ElegantJapaneseScreen.XXXYourTargetRoute.name) {
navController.graph.startDestinationRoute?.let {
popUpTo(it) {
saveState = true
}
}
launchSingleTop = true
restoreState = true
}
關鍵解說:
navController.navigate
:主要是透過這邊指定目標名稱去跳轉saveState = true
:這個選項表示在退回上頁時保存狀態,以便將來能恢復。- ` launchSingleTop = true`:這個選項表示如果目標頁面已經在Stack的頂部,則不會建立新的實例,而是重複使用現有的實例。這對於避免重複實例化目標頁面非常有用。
- ` restoreState = true`:這個選項表示在導航時,如果目標路由之前已經存在且已儲存,則恢復其狀態。這有助於在導航回目標路由時保持其狀態不變。
- 以上這邊都可以依照自己的需求去調整
另外也可以透過navigateUp
回上一頁
navController.navigateUp()