サイトロゴ

Enjoy Creating
Web & Mobile Apps

MENU BOX
WEB
MOBILE
OPEN

ホーム

 > 

 > 

【Jetpack Compose】ナビゲーション(画面間の移動)を実装する方法

【Jetpack Compose】ナビゲーション(画面間の移動)を実装する方法

この記事にはプロモーションが含まれています。

Webサイトであれば、HTMLでa(アンカー)タグのhref属性にファイルパスを指定するだけで、簡単にリンクの設定 = 画面の移動 を設定することができますが、Androidアプリではやや複雑な手順を踏まなければなりません。

なので、特にWebサイト制作からAndroidアプリ制作に移行したり、活動の幅を広げたりした人にとっては、『画面(ページ)の移動だけでこんなに大変なの!?』と、最初は少しびっくりするかもしれません。

ですが、Jetpack ComposeはAndroidアプリを効率的に開発できるように設計されており、日々進化を重ねているので、慣れてしまえばけっこう簡単に感じられる程度ではあります。

この記事では、Jetpack Composeを使ったナビゲーションの基本について説明していきます。

この記事を読むことで分かること
  • ・Jetpack Composeでナビゲーションを実装する方法
  • ・画面遷移のトランジションをカスタマイズする方法

– 目次 –

依存関係の設定

まず最初に、Jetpack Compose用のナビゲーションライブラリを依存関係に追加しておきましょう。

モジュールレベルの build.gradle.kts ファイルのdependenciesブロックに次のコードを追加します。(バージョンはその時点での最新の安定版に設定することをオススメします)

build.gradle.kts(Module :app)

// Enable Navigation
implementation("androidx.navigation:navigation-compose:2.7.5")

ナビゲーションライブラリの最新の安定バージョンは、Android Developer公式ページから確認できます。

依存関係の設定が完了したら、Sync Now ボタンをクリックして同期を完了させておきましょう。

この時点で開発が行き詰まってしまうような難しいポイントは特にないかと思いますが、ライブラリのバージョンの設定によっては、他の依存関係との問題が生じてエラーを起こす可能性があるので、その点だけ注意しておくと良いかと思います。

念のため、この時点で一度ビルドして、問題なくアプリが起動できることを確認しておくのも良い手法です。

万が一、同期やビルドでエラーが発生した場合は、落ち着いてエラーメッセージを確認してエラーの原因を特定すれば大丈夫です。

現時点では、モジュールレベルの build.gradle.kts ファイルしか手を付けていないわけですから、問題があるとしたらライブラリのバージョンや、compileSdk などの設定でほぼ間違いありません。

エラーメッセージをしっかり確認して、問題のある依存関係の設定をやり直すことで、この手のエラーはほとんど解決できるかと思います。

    POINT!
  1. ・Jetpack Composeでナビゲーションを利用するには、まず依存関係の設定を行う!
  2. ・依存関係の設定が完了したら、Sync Nowボタンを押して同期を完了させておこう!
  3. ・万が一エラーが起こっても、落ち着いてエラーメッセージを確認すれば大丈夫!

スクリーン(画面)の作成

画面移動のナビゲーションを実装するには、当然ですがアプリの画面(スクリーン)を複数用意しておかなければなりません。

たとえば、画面Aから画面Bに移動するには、少なくともコンポーザブルAとコンポーザブルBが必要です。

この記事では、HomeScreenAScreenB の3つのコンポーザブルを用意し、それらを使って解説を進めていきます。

画面構成のイメージは下のイメージ図をご覧ください。

この記事におけるサンプルアプリの画面構成イメージ図

Home 画面から ScreenAScreenB に画面遷移することができ、それぞれの画面から Home に戻れるといった、シンプルな構造になっています。

本来ならば、ScreenAScreenB は、全く異なるデザインにした方がどちらの画面に遷移したかが分かりやすくなって良いのですが、デザインを細かく作り込むとコードも複雑になってしまいます。

なので、今回はコンポーザブルに表示されるテキストと色に差をつけることで、ScreenAScreenB の違いを表現します。

今回はあくまでナビゲーションの解説がテーマですので、各コンポーザブルのコードのうち、ナビゲーションとは関係ない部分については特に触れません。

Home コンポーザブルには ScreenAScreenB に遷移するためのボタンを配置し、ScreenA には ScreenA であることを示すテキストと Home に戻るためのボタン、ScreenB には ScreenB であることを示すテキストと Home に戻るボタンを用意しておきます。

ScreenAScreenB はほぼ同じ画面構成であるため、CommonScreen コンポザーブルを用意し、それに渡す引数を変えることで表示されるテキストと色を変えることとします。

また、現時点ではまだ画面遷移するための関数は定義できませんが、各コンポーザブルには引数を受け取らず、何も返さない関数 () -> Unit を受け取れるようにしておきます。

Home コンポーザブルは次のように定義できます。

Home.kt

@Composable
fun Home(
   toScreenA: () -> Unit,
   toScreenB: () -> Unit,
   modifier: Modifier = Modifier
) {
   Column(
       verticalArrangement = Arrangement.Center,
       horizontalAlignment = Alignment.CenterHorizontally,
       modifier = modifier
           .fillMaxSize()
   ) {

       Row(
           verticalAlignment = Alignment.CenterVertically,
           modifier = Modifier
               .padding(bottom = 32.dp)
       ) {
           Icon(
               imageVector = Icons.Default.Home,
               contentDescription = null,
               modifier = Modifier
                   .size(50.dp)
           )
           Text(
               text = "HOME",
               fontSize = 32.sp,
               fontWeight = FontWeight.Bold
           )
       }

       Button(onClick = toScreenA ) {
           Text(text = "To ScreenA")
       }

       Spacer(modifier = Modifier.height(32.dp))

       Button(onClick = toScreenB ) {
           Text(text = "To ScreenB")
       }
   }
}
Homeコンポーザブルのイメージ画像

ホーム画面であることを分かりやすくするためにアイコンなどを配置しているので、ややコードが多いように感じられるかもしれませんが、ナビゲーションに関わる部分は画面遷移のために配置している2つのボタンのみです。

そして次に、ScreenAScreenB の共通デザインを担当する CommonScreen コンポーザブルのコードを示します。

CommonScreen.kt

@Composable
fun CommonScreen(
   modifier: Modifier = Modifier,
   screenName: String,
   screenColor: Color,
   toHome: () -> Unit
) {
   Surface(
       color = screenColor,
       modifier = modifier
   ) {
       Column(
           verticalArrangement = Arrangement.Center,
           horizontalAlignment = Alignment.CenterHorizontally,
           modifier = Modifier
               .fillMaxSize()
       ) {
           Text(
               text = "This screen is $screenName",
               fontSize = 32.sp,
               fontWeight = FontWeight.Bold,
           )
           ElevatedButton(
               onClick = toHome,
               modifier = Modifier
                   .padding(top = 16.dp)
           ) {
               Text(text = "HOME")
           }
       }
   }
}

@Preview(showBackground = true)
@Composable
fun CommonScreenPreview() {
   CommonScreen(
       screenName = "Test", 
       screenColor = MaterialTheme.colorScheme.primary
   ) { }
}
各スクリーンの共通画面のイメージ画像

CommonScreenでは、引数を3つ受け取っています。(modifierを除く)

screenName は、画面の名称をテキストとして表示するための文字列で、screenColor は、画面に色を指定するための Color 型の色になります。

また、Home に戻るための関数として、toHome をパラメータに指定しています。

この CommonScreen を元に、ScreenAScreenB は次のように定義することができます。

ScreenA(B).kt

@Composable
fun ScreenA(
   modifier: Modifier = Modifier,
   toHome: () -> Unit
) {
   CommonScreen(
       modifier = modifier,
       screenName = "ScreenA",
       screenColor = MaterialTheme.colorScheme.primary,
       toHome = toHome
   )
}

@Composable
fun ScreenB(
   modifier: Modifier = Modifier,
   toHome: () -> Unit
) {
   CommonScreen(
       modifier = modifier,
       screenName = "ScreenB",
       screenColor = MaterialTheme.colorScheme.secondary,
       toHome = toHome
   )
}

この設定により、ScreenA ならば “This screen is ScreenA” と表示され、画面のカラーはPrimary(デフォルトでは紫色)で表示されることになります。
一方、ScreenB では表示テキストとカラーが変化するため、画面が遷移した時、どちらの画面(コンポーザブル)に遷移したかが一目で分かります。

これでナビゲーションを実装するための準備が整いました!

あとはルートとなるコンポーザブルを用意して、ナビゲーションに必要な NavControllerNavHost と呼ばれるものを定義すれば、ナビゲーション(画面遷移)を実装することができます。

これらの手順については、次のセクションで解説を進めていきます。

    POINT!
  1. ・当然だが、画面遷移の実装には複数の画面(コンポーザブル)を準備しておく必要がある!
  2. ・画面遷移を行う関数は、() -> Unit の形式で渡す(受け取れる)ようにしておこう!
  3. ・画面遷移を視覚的に確認しやすくするために、テキストや色に変化をつけておくと良い!

ルートを用意してナビゲーションを実装する

HomeScreenAScreenB を包括するコンポーザブルとして Root コンポーザブルを作成し、画面遷移に必要不可欠な navController を定義します。

ここまでで、コードは下のようになります。

Root.kt

@Composable
fun Root(modifier: Modifier = Modifier) {
   // Create NavController
   val navController = rememberNavController()
}

そして、Root コンポーザブル内で、ナビゲーションをホストする NavHost を定義します。このとき、navController パラメータには先程定義したばかりの navController を渡せばOKです。

現時点で、Root コンポーザブルの NavHost 部分のコードは次のようになります。

Root.kt

// Create NavHost
NavHost(
   navController = navController,
   startDestination = 
) {

}

NavHost に渡す必要のあるもう一つのパラメータの startDestination ですが、これはその名の通り、起点となる画面(コンポーザブル)の名称を文字列型で指定します。

今回のサンプルでは Home コンポーザブルが起点画面となるので、それが分かる名称であれば何でも良いでしょう。

普通に、”HOME” というふうに文字列を直接入力しても良いのですが、今回はKotlinの enum class を利用して設定していきたいと思います。

Root コンポーザブルの外で、次のようにナビゲーションを enum class で定義します。

Root.kt

enum class Screens {
   HOME,
   SCREEN_A,
   SCREEN_B
}

enum class でナビゲーションの名称を指定することで、単純な文字の入力ミスを防げるようになるだけでなく、ナビゲーションを様々な型で扱えるようになることにより、コードに柔軟性が生まれます。

たとえば、ordinal で数値(Int)型、name で文字列(String)型として enum class で定義された値を扱うことができます。

今回は、startDestination パラメータに指定する文字列型として扱う必要があるので、name プロパティを利用しましょう。

コードは次のようになります。

Root.kt

// Create NavHost
NavHost(
   navController = navController,
   startDestination = Screens.HOME.name
) {

}

あとは、NavHost のスコープ内に、composable を利用して各コンポーザブルをセッティングしていきます。

composableroute パラメータには、当該コンポーザブルの名称を渡します。startDestination と同様に、enum class で定義したものを name プロパティで文字列に変換して渡す必要があります。

ここまでの手順でコードは次のようになります。

Root.kt

NavHost(
   navController = navController,
   startDestination = Screens.HOME.name
) {
   composable(Screens.HOME.name) {
       Home(
           toScreenA = {},
           toScreenB = {},
           modifier = modifier
       )
   }

   composable(Screens.SCREEN_A.name) {
       ScreenA(toHome = {})
   }

   composable(Screens.SCREEN_B.name) {
       ScreenB(toHome = {})
   }
}

ここまで来れば完成まであと少しです!

このままでは各コンポーザブルに配置されたボタンを押しても何も起こらないので、ボタンが押されると画面遷移が起こるように、navControllernavigate 関数を指定したものを、各コンポーザブルに渡すようにします。

navigate 関数の route パラメータには、画面の名称を文字列で渡す必要があるので、これまでと同じ手法で設定していきます。

コードは次のようになります。

Root.kt

NavHost(
   navController = navController,
   startDestination = Screens.HOME.name
) {
   composable(Screens.HOME.name) {
       Home(
           toScreenA = { navController.navigate(Screens.SCREEN_A.name) },
           toScreenB = { navController.navigate(Screens.SCREEN_B.name) },
           modifier = modifier
       )
   }

   composable(Screens.SCREEN_A.name) {
       ScreenA(toHome = { navController.navigate(Screens.HOME.name)})
   }

   composable(Screens.SCREEN_B.name) {
       ScreenB(toHome = { navController.navigate(Screens.HOME.name)})
   }
}

これでJetpack Composeを使ったナビゲーションの実装ができました!

動作は下の動画のようになります。

ナビゲーションはこれで完成ですが、もうひと工夫加えたいという方のために、最後のセクションでは画面遷移をアニメーションにする方法をご紹介します。

    POINT!
  1. ・ルートとなるコンポーザブルでnavControllerを定義し、NavHostのnavControllerに渡そう!
  2. ・startDestinationには、起点となるコンポーザブルのスクリーンの名称(文字列)を指定しよう!
  3. ・スクリーンの名称は enum classで管理すると何かと便利!

画面遷移のトランジション

画面遷移のトランジションを指定しない場合、デフォルトでは移動元の画面と移動先の画面の透明度が変化することで画面遷移が行われます。

動画編集を行ったことがある方であれば、『ディゾルブ(Dissolve)』の効果をイメージすると分かりやすいでしょう。シンプルで汎用性の高いトランジションですね。

地味ではありますが、何も指定しなくても自然な画面遷移にはなるので、特にこだわらない場合はデフォルトのままでも良いでしょう。

ただし、画面遷移の挙動をコントロールしたい場合は追加の設定が必要です。

ここでは、遷移先の画面がスライドイン・スライドアウトするように設定するシンプルな方法をご紹介します。


1:左右からスライドイン・スライドアウトさせる

composable()enterTransitionexitTransition パラメータに、値の指定を追加します。

左右のスライドは slideIn(Out)Horizontally を指定すればOKです。

この時、左と右のどちらの方向からスライドさせるかによって、initialOffsetXtargetOffsetX の値の指定が変わります。

画面遷移のトランジションに関係するコードは次の通りです。(トランジションは300ミリ秒に指定しています)

Root.kt

composable(
   Screens.SCREEN_A.name,
   enterTransition = {
       slideInHorizontally(
           animationSpec = tween(300),
           initialOffsetX = { -it } // from left
       )
   },
   exitTransition = {
       slideOutHorizontally(
           animationSpec = tween(300),
           targetOffsetX = { -it }
       )
   }
) {
   ScreenA(toHome = { navController.navigate(Screens.HOME.name) })
}

composable(
   Screens.SCREEN_B.name,
   enterTransition = {
       slideInHorizontally(
           animationSpec = tween(300),
           initialOffsetX = { it } // from right
       )
   },
   exitTransition = {
       slideOutHorizontally(
           animationSpec = tween(300),
           targetOffsetX = { it }
       )
   }
) {
   ScreenB(toHome = { navController.navigate(Screens.HOME.name) })
}

2:上下からスライドイン・スライドアウトさせる

指定方法はほとんど同じですが、slideIn(Out)Horizontally ではなく slideIn(Out)Vertically になるのと、initial(target)OffsetX ではなく initial(target)OffsetY に変わります

コードは次のようになります。

Root.kt

composable(
   Screens.SCREEN_A.name,
   enterTransition = {
       slideInVertically(
           animationSpec = tween(300),
           initialOffsetY = { -it } // from top
       )
   },
   exitTransition = {
       slideOutVertically(
           animationSpec = tween(300),
           targetOffsetY = { -it }
       )
   }
) {
   ScreenA(toHome = { navController.navigate(Screens.HOME.name) })
}

composable(
   Screens.SCREEN_B.name,
   enterTransition = {
       slideInVertically(
           animationSpec = tween(300),
           initialOffsetY = { it } // from bottom
       )
   },
   exitTransition = {
       slideOutVertically(
           animationSpec = tween(300),
           targetOffsetY = { it }
       )
   }
) {
   ScreenB(toHome = { navController.navigate(Screens.HOME.name) })
}

画面遷移を左右・上下方向のスライドにすることができました!

アプリの画面構成が2〜3画面程度であれば、ナビゲーションを利用するよりも、条件分岐でコンポーザブルの表示を切り替えた方が楽で効率的な場合もありますが、それ以上の画面が必要な場合は素直にナビゲーションを利用した方が良いかと思います。

また、今回は画面遷移の方法だけに解説の的を絞ったので触れることができませんでしたが、たとえば共通のトップバーに現在表示されている画面(コンポーザブル)の名称を表示させたりなど、もう一歩踏み込んだことをしたければ、さらに複雑な手順が必要となります。

そのあたりは、また別の記事としてご紹介できればと思います!

    POINT!
  1. ・画面遷移のトランジションを調整するには、enterTransitionとexitTransitionパラメータに値を設定する!
  2. ・左右のスライドは、slideIn(Out)Horizontally!
  3. ・上下のスライドは、slideIn(Out)Vertically!

« »

カテゴリーリンク

著者について- author profile -

ROYDOプロフィール写真
Michihiro

モバイルアプリ(iOS・Android)ディベロッパー&デザイナー

これまでに、可読性の高いカラーパターンを自動で生成するアプリや、『第3火曜日』といった形式で通知をスケジュールできるアプリなどを制作。

サブでWebデザイン・フロントエンドエンジニアとしても活動しています。

📝ツール・言語:JavaScript/React Native/Kotlin/Android Studio/Swift/SwiftUI

🎓資格:基本情報技術者/ウェブデザイン技能検定3級

Twitterアイコン Instagramアイコン