1. 概要
Android プラットフォームでアプリを開発することの大きなメリットの 1 つは、ウェアラブル、折りたたみ式、タブレット、デスクトップ、さらにはテレビなど、さまざまなフォーム ファクタでユーザーにリーチできることです。アプリを使用するユーザーは、同じアプリを大画面デバイスで使用して、広くなったスペースのメリットを享受できます。Android ユーザーが、画面サイズの異なる複数のデバイスでアプリを使用することが多くなり、すべてのデバイスで質の高いユーザー エクスペリエンスを期待するようになっています。
ここまでは、主にモバイル デバイス向けアプリを作成する方法を学習しましたが、この Codelab では、アプリを他の画面サイズに適応させる方法を学びます。ここでは、アダプティブ ナビゲーション レイアウト パターンという、折りたたみ式、タブレット、パソコンなど、モバイル デバイスと大画面デバイスのどちらでも使用可能で見栄えも良いパターンを使用します。
前提条件
- クラス、関数、条件文など、Kotlin プログラミングに精通していること
ViewModel
クラスの使用経験Composables
関数の作成に精通していること- Jetpack Compose でレイアウトを作成した経験
- デバイスまたはエミュレータでアプリを実行した経験
学習内容
- シンプルなアプリで、ナビゲーション グラフを使用せずに画面間のナビゲーションを作成する方法
- Jetpack Compose を使用してアダプティブ ナビゲーション レイアウトを作成する方法
- カスタムの「戻る」ハンドラを作成する方法
作成するアプリの概要
- 既存の Reply アプリにダイナミック ナビゲーションを実装し、そのレイアウトがすべての画面サイズに適応するようにする
完成すると以下の画像のようになります。
必要なもの
- インターネットにアクセスできるパソコン、ウェブブラウザ、Android Studio
- GitHub へのアクセス
2. アプリの概要
Reply アプリの概要
Reply アプリは、メール クライアントに似たマルチスクリーン アプリです。
4 つのカテゴリがあり、それぞれが受信トレイ、送信済み、下書き、迷惑メールの各タブに表示されます。
スターター コードをダウンロードする
Android Studio で basic-android-kotlin-compose-training-reply-app
フォルダを開きます。
- プロジェクト用に提供されている GitHub リポジトリ ページに移動します。
- ブランチ名が Codelab で指定されたブランチ名と一致していることを確認します。たとえば、次のスクリーンショットでは、ブランチ名は main です。
- プロジェクトの GitHub ページで、[Code] ボタンをクリックすると、ポップアップが表示されます。
- ポップアップで、[Download ZIP] をクリックして、プロジェクトをパソコンに保存します。ダウンロードが完了するまで待ちます。
- パソコンに保存したファイルを見つけます([ダウンロード] フォルダなど)。
- ZIP ファイルをダブルクリックして展開します。プロジェクト ファイルが入った新しいフォルダが作成されます。
Android Studio でプロジェクトを開く
- Android Studio を起動します。
- [Welcome to Android Studio] ウィンドウで、[Open] をクリックします。
注: Android Studio がすでに開いている場合は、メニューから [File] > [Open] を選択します。
- ファイル ブラウザで、展開したプロジェクト フォルダがある場所([ダウンロード] フォルダなど)に移動します。
- そのプロジェクト フォルダをダブルクリックします。
- Android Studio でプロジェクトが開かれるまで待ちます。
- 実行ボタン をクリックして、アプリをビルドし、実行します。期待どおりにビルドされることを確認します。
3. スターター コードのチュートリアル
Reply アプリの重要なディレクトリ
Reply アプリ プロジェクトのデータと UI レイヤは、別々のディレクトリに分割されています。ReplyViewModel
、ReplyUiState
などのコンポーザブルは、ui
ディレクトリにあります。データレイヤとデータ プロバイダのクラスを定義する data
クラスと enum
クラスは data
ディレクトリにあります。
Reply アプリのデータ初期化
Reply アプリは ReplyViewModel
の initilizeUIState()
メソッドを使用してデータが初期化されます。このメソッドは init
関数で実行されます。
ReplyViewModel.kt
...
init {
initializeUIState()
}
private fun initializeUIState() {
var mailboxes: Map<MailboxType, List<Email>> =
LocalEmailsDataProvider.allEmails.groupBy { it.mailbox }
_uiState.value =
ReplyUiState(
mailboxes = mailboxes,
currentSelectedEmail = mailboxes[MailboxType.Inbox]?.get(0)
?: LocalEmailsDataProvider.defaultEmail
)
}
...
画面レベルのコンポーザブル
他のアプリと同様に、Reply アプリは viewModel
と uiState
が宣言されるメインのコンポーザブルとして ReplyApp
コンポーザブルを使用します。さまざまな viewModel
関数も、ReplyHomeScreen
コンポーザブルのラムダ引数として渡されます。
ReplyApp.kt
...
@Composable
fun ReplyApp(modifier: Modifier = Modifier) {
val viewModel: ReplyViewModel = viewModel()
val replyUiState = viewModel.uiState.collectAsState().value
ReplyHomeScreen(
replyUiState = replyUiState,
onTabPressed = { mailboxType: MailboxType ->
viewModel.updateCurrentMailbox(mailboxType = mailboxType)
viewModel.resetHomeScreenStates()
},
onEmailCardPressed = { email: Email ->
viewModel.updateDetailsScreenStates(
email = email
)
},
onDetailScreenBackPressed = {
viewModel.resetHomeScreenStates()
},
modifier = modifier
)
}
その他のコンポーザブル
ReplyHomeScreen.kt
: ナビゲーション要素など、ホーム画面用の画面コンポーザブルが含まれています。ReplyHomeContent.kt
: ホーム画面の詳細なコンポーザブルを定義するコンポーザブルが含まれています。ReplyDetailsScreen.kt
: 画面コンポーザブルと、詳細画面用のより小さなコンポーザブルが含まれています。
この Codelab の次のセクションに進む前に、各ファイルについて詳しく確認して、これらのコンポーザブルの理解を深めることをおすすめします。
4. ナビゲーション グラフを使用せずに画面を変更する
前のパスウェイでは、NavHostController
クラスを使用して、ある画面から別の画面に移動する方法を学びました。Compose では、実行時の可変状態を利用し、単純な条件文で画面を変更することもできます。これは、Reply アプリのような 2 つの画面間のみで切り替える小さなアプリなどに特に有用です。
状態変更で画面を変更する
Compose では、状態変更が発生すると画面が再コンポーズされます。単純な条件文を使用して画面を変更することで、状態の変化に対応できます。
条件文を使用して、ユーザーがホーム画面にいるときにホーム画面の内容を表示し、ユーザーがホーム画面にいないときには詳細画面の内容を表示しましょう。
Reply アプリを変更して、状態変更時に画面を変更できるようにします。手順は次のとおりです。
- Android Studio でスターター コードを開きます。
ReplyHomeScreen.kt
のReplyHomeScreen
コンポーザブルで、ReplyAppContent
コンポーザブルを、replyUiState
オブジェクトのisShowingHomepage
プロパティがtrue
の場合のif
文で囲みます。
ReplyHomeScreen.kt
@Composable
fun ReplyHomeScreen(
replyUiState: ReplyUiState,
onTabPressed: (MailboxType) -> Unit = {},
onEmailCardPressed: (Int) -> Unit = {},
onDetailScreenBackPressed: () -> Unit = {},
modifier: Modifier = Modifier
) {
...
if (replyUiState.isShowingHomepage) {
ReplyAppContent(
replyUiState = replyUiState,
onTabPressed = onTabPressed,
onEmailCardPressed = onEmailCardPressed,
navigationItemContentList = navigationItemContentList,
modifier = modifier
)
}
}
詳細画面を表示することで、ユーザーがホーム画面にいないときのシナリオを考慮する必要があります。
- 本体に
ReplyDetailsScreen
コンポーザブルが含まれるelse
分岐を追加します。ReplyDetailsScreen
コンポーザブルの引数としてreplyUIState
、onDetailScreenBackPressed
、modifier
を追加します。
ReplyHomeScreen.kt
@Composable
fun ReplyHomeScreen(
replyUiState: ReplyUiState,
onTabPressed: (MailboxType) -> Unit = {},
onEmailCardPressed: (Int) -> Unit = {},
onDetailScreenBackPressed: () -> Unit = {},
modifier: Modifier = Modifier
) {
...
if (replyUiState.isShowingHomepage) {
ReplyAppContent(
replyUiState = replyUiState,
onTabPressed = onTabPressed,
onEmailCardPressed = onEmailCardPressed,
navigationItemContentList = navigationItemContentList,
modifier = modifier
)
} else {
ReplyDetailsScreen(
replyUiState = replyUiState,
onBackPressed = onDetailScreenBackPressed,
modifier = modifier
)
}
}
replyUiState
オブジェクトは状態オブジェクトです。そのため、実行時には、replyUiState
オブジェクトの isShowingHomepage
プロパティが変化すると、ReplyHomeScreen
コンポーザブルが再コンポーズされ、if/else
文が再評価されます。このアプローチでは、NavHostController
クラスを使用することなく画面間のナビゲーションに対応しています。
カスタムの「戻る」ハンドラを作成する
NavHost
コンポーザブルを使用して画面を切り替えることの利点の 1 つは、前の画面の履歴がバックスタックに保存されることです。これらの保存された画面により、システムの [戻る] ボタンを押すと前の画面に簡単に移動できるようになります。Reply アプリでは NavHost
を使用しないため、[戻る] ボタンを手動で処理するためのコードを追加する必要があります。これは次で行います。
次の手順に沿って、Reply アプリでカスタムの「戻る」ハンドラを作成します。
ReplyDetailsScreen
コンポーザブルの 1 行目に、BackHandler
コンポーザブルを追加します。BackHandler
コンポーザブルの本体でonBackPressed()
関数を呼び出します。
ReplyDetailsScreen.kt
...
import androidx.activity.compose.BackHandler
...
@Composable
fun ReplyDetailsScreen(
replyUiState: ReplyUiState,
modifier: Modifier = Modifier,
onBackPressed: () -> Unit = {},
) {
BackHandler {
onBackPressed()
}
...
5. 大画面デバイスでアプリを実行する
サイズ変更可能なエミュレータでアプリを確認する
使いやすいアプリを作成するには、さまざまなフォーム ファクタでのユーザー エクスペリエンスを理解する必要があります。そのため、開発プロセスの初期から、さまざまなフォーム ファクタでアプリをテストする必要があります。
さまざまな画面サイズのエミュレータを使用することで、この目標を達成できます。特に複数の画面サイズに対応するアプリを一度に構築する場合には、面倒な作業になる可能性があります。また、実行中のアプリの画面サイズの変化(画面の向きの変更、デスクトップでのウィンドウ サイズの変更、折りたたみ式デバイスでの折りたたみ状態の変化など)に対する反応をテストする必要があります。
Android Studio では、サイズ変更可能なエミュレータを導入することで、このようなシナリオのテストが可能になりました。
サイズ変更可能なエミュレータは、次の手順でセットアップします。
- Android Studio Chipmunk | 2021.2.1 以降が実行されるようにします。
- Android Studio で、[Tools] > [Device Manager] を選択します。
- [Device Manager] で [Create device] をクリックします。
- [Phone] カテゴリと [Resizable (Experimental)] デバイスを選択します。
- [Next] をクリックします。
- API レベル 33 を選択します。
- [Next] をクリックします。
- 新しい Android Virtual Device に名前を付けます。
- [Finish] をクリックします。
大画面エミュレータでアプリを実行する
サイズ変更可能なエミュレータのセットアップが完了したので、大画面でアプリがどのように表示されるのかを見てみましょう。
- サイズ変更可能なエミュレータでアプリを実行します。
- 表示モードに [Tablet] を選択します。
- 横表示のタブレット モードでアプリを調べます。
タブレット画面の表示が横方向に引き伸ばされています。この向きは機能的には問題ありませんが、大画面のスペースを最大限に活用できてはいません。次はこれに対処しましょう。
大画面用に設計する
タブレットでこのアプリを見たとき、デザインが悪く、魅力的でないと思ったのではないでしょうか。そうです。このレイアウトは大画面での使用に適したデザインになっていません。
タブレットや折りたたみ式などの大画面向けに設計する際には、ユーザーの使い勝手や、ユーザーの指と画面との距離を考慮する必要があります。モバイル デバイスの場合、ユーザーの指は画面のほとんどの場所に簡単に届くため、インタラクティブ要素(ボタンやナビゲーション要素など)の位置はそれほど重要ではありません。しかし、大画面の場合、重要なインタラクティブ要素が画面の中央に配置されると、指が届きづらくなります。
この Reply アプリで見たように、画面に合わせて UI 要素を広げたり引き伸ばしたりするだけでは、大画面向けのデザインとは言えません。広くなったスペースを活用して、別のユーザー エクスペリエンスを生み出すチャンスです。たとえば、同じ画面に別のレイアウトを追加して別の画面への移動を避けることや、マルチタスクを可能にすることが考えられます。
この設計により、ユーザーの生産性が高まり、エンゲージメントが促進されます。ただし、このデザインを採用する前に、まず画面サイズごとに異なるレイアウトを作成する方法を確認してください。
6. さまざまな画面サイズにレイアウトを合わせる
ブレークポイントとは
同じアプリで異なるレイアウトをどのように表示できるのか疑問に思われるかもしれません。簡単に言えば、この Codelab の最初に行ったように、さまざまな状態の条件分岐を使用するということです。
アダプティブ アプリを作成するには、画面サイズに応じてレイアウトを変更する必要があります。レイアウト変更の基準となる測定ポイントをブレークポイントと呼びます。マテリアル デザインで、ほとんどの Android 画面に対応する独自のブレークポイント範囲が作成されました。
このブレークポイント範囲の表は、たとえば、画面サイズが 600 dp 未満のデバイスでアプリが実行されている場合は、モバイル レイアウトが表示される必要があることが示されています。
ウィンドウ サイズクラスを使用する
Compose に導入された WindowSizeClass
API を使用すると、マテリアル デザインのブレークポイントを簡単に実装できます。
ウィンドウ サイズクラスには、幅と高さの両方について、コンパクト、中程度、拡大の 3 つのカテゴリのサイズがあります。
次の手順に沿って、Reply アプリに WindowSizeClass
API を実装します。
material3-window-size-class
依存関係をモジュールbuild.gradle
ファイルに追加します。
build.gradle
...
dependencies {
...
"androidx.compose.material3:material3-window-size-class:$material3_version"
...
- [Sync Now] をクリックして、依存関係を追加した後に Gradle を同期させます。
build.grade
ファイルが最新の状態になると、いつでもアプリのウィンドウ サイズを格納する変数を作成できます。
MainActivity.kt
ファイルのonCreate()
関数内で、パラメータとしてthis
コンテキストを渡してcalculateWindowSizeClass
メソッドを呼び出した結果をwindowSize
という変数に代入します。- 適切な
calculateWindowSizeClass
パッケージをインポートします。
MainActivity.kt
...
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ReplyTheme {
val windowSize = calculateWindowSizeClass(this)
ReplyApp()
...
calculateWindowSizeClass
の箇所に赤い下線が引かれ、赤い電球が表示されています。windowSize
変数の左側にある赤い電球のアイコンをクリックし、[Opt in for ‘ExperimentalMaterial3WindowSizeClassApi' on ‘onCreate'] を選択すると、onCreate()
メソッドの上にアノテーションが作成されます。
MainActivity.kt
の WindowWidthSizeClass
変数を使用して、さまざまなコンポーザブルに表示するレイアウトを決定できます。この値を受け取るように ReplyApp
コンポーザブルを準備しましょう。
ReplyApp.kt
ファイルで、WindowWidthSizeClass
をパラメータとして受け取るようにReplyApp
コンポーザブルを変更し、適切なパッケージをインポートします。
ReplyApp.kt
...
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
...
@Composable
fun ReplyApp(
windowSize: WindowWidthSizeClass,
modifier: Modifier = Modifier
) {
...
MainActivity.kt
ファイルのonCreate()
メソッドで、windowSize
変数をReplyApp
コンポーネントに渡します。
MainActivity.kt
...
setContent {
ReplyTheme {
val windowSize = calculateWindowSizeClass(this)
ReplyApp(
windowSize = windowSize.widthSizeClass
)
...
windowSize
パラメータに関しては、アプリのプレビューも更新する必要があります。
WindowWidthSizeClass.Compact
をwindowSize
パラメータとしてプレビュー コンポーネントのReplyApp
コンポーザブルに渡し、適切なパッケージをインポートします。
MainActivity.kt
...
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
...
@Preview(showBackground = true)
@Composable
fun ReplyAppPreview() {
ReplyTheme {
ReplyApp(
windowSize = WindowWidthSizeClass.Compact,
)
}
}
- 画面のサイズに基づいてアプリのレイアウトを変更するために、
ReplyApp
コンポーザブルにWindowWidthSizeClass
値に基づくwhen
文を追加します。
ReplyApp.kt
...
@Composable
fun ReplyApp(
windowSize: WindowWidthSizeClass,
modifier: Modifier = Modifier
) {
val viewModel: ReplyViewModel = viewModel()
val replyUiState = viewModel.uiState.collectAsState().value
when (windowSize) {
WindowWidthSizeClass.Compact -> {
}
WindowWidthSizeClass.Medium -> {
}
WindowWidthSizeClass.Expanded -> {
}
else -> {
}
}
...
これで、WindowSizeClass
の値を使用してアプリのレイアウトを変更するための基盤が出来上がりました。次のステップでは、各画面サイズでアプリをどのように表示するかを決定します。
7. アダプティブ ナビゲーション レイアウトを実装する
アダプティブ UI ナビゲーションを実装する
現在は、すべての画面サイズでボトム ナビゲーションが使用されます。
前述のように、このナビゲーション要素は、大画面では重要なナビゲーション要素にアクセスするのが難しいため、理想的とは言えません。幸い、レスポンシブ UI のナビゲーションでは、ウィンドウ サイズクラスごとにナビゲーション要素の推奨パターンがあります。Reply アプリの場合は、以下の要素を実装できます。
マテリアル デザインのナビゲーション コンポーネントであるナビゲーション レールでは、主要なデスティネーション用のコンパクトなナビゲーション オプションにアプリの端からアクセスできます。
同様に、永続 / 固定ナビゲーション ドロワーは、大画面でアクセスしやすいように作られたマテリアル デザインの別のオプションです。
ナビゲーション ドロワーを実装する
拡大画面用のナビゲーション ドロワーを作成するために、navigationType
パラメータを使用します。以下の手順に沿って進めます。
- ナビゲーション要素の種類を表現するために、
ui
ディレクトリにある新しいパッケージutils
に新しいファイルWindowStateUtils.kt
を作成します。 - ナビゲーション要素の種類を表す
Enum
クラスを追加します。
WindowStateUtils.kt
package com.example.reply.ui.utils
enum class ReplyNavigationType {
BOTTOM_NAVIGATION, NAVIGATION_RAIL, PERMANENT_NAVIGATION_DRAWER
}
ナビゲーション ドロワーを実装するには、アプリのウィンドウ サイズに基づいてナビゲーション タイプを決定する必要があります。
ReplyApp
コンポーザブルで、navigationType
変数を作成し、when
文の画面サイズに応じて適切なReplyNavigationType
の値を代入します。
ReplyApp.kt
...
import com.example.reply.ui.utils.ReplyNavigationType
...
when (windowSize) {
WindowWidthSizeClass.Compact -> {
navigationType = ReplyNavigationType.BOTTOM_NAVIGATION
}
WindowWidthSizeClass.Medium -> {
navigationType = ReplyNavigationType.NAVIGATION_RAIL
}
WindowWidthSizeClass.Expanded -> {
navigationType = ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER
}
else -> {
navigationType = ReplyNavigationType.BOTTOM_NAVIGATION
}
}
...
これで ReplyHomeScreen
コンポーザブルで navigationType
の値を使用できるようになりました。実際に使用するために、それをコンポーザブルのパラメータにします。
ReplyHomeScreen
コンポーザブルで、navigationType
をパラメータとして追加します。
ReplyHomeScreen.kt
...
@Composable
fun ReplyHomeScreen(
navigationType: ReplyNavigationType,
replyUiState: ReplyUiState,
onTabPressed: (MailboxType) -> Unit = {},
onEmailCardPressed: (Email) -> Unit = {},
onDetailScreenBackPressed: () -> Unit = {},
modifier: Modifier = Modifier
)
...
navigationType
をReplyHomeScreen
コンポーザブルに渡します。
ReplyApp.kt
...
ReplyHomeScreen(
navigationType = navigationType,
replyUiState = replyUiState,
onTabPressed = { mailboxType: MailboxType ->
viewModel.updateCurrentMailbox(mailboxType = mailboxType)
viewModel.resetHomeScreenStates()
},
onEmailCardPressed = { email: Email ->
viewModel.updateDetailsScreenStates(
email = email
)
},
onDetailScreenBackPressed = {
viewModel.resetHomeScreenStates()
},
modifier = modifier
)
...
次に、分岐を作成して、ユーザーが拡張画面でアプリを開いてホーム画面を表示したときに、アプリのコンテンツとともにナビゲーション ドロワーを表示するようにします。
ReplyHomeScreen
コンポーザブルの本体で、navigationType == ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER && replyUiState.isShowingHomepage
という条件のif
文を追加します。
ReplyHomeScreen.kt
import androidx.compose.material3.PermanentNavigationDrawer
...
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ReplyHomeScreen(
navigationType: ReplyNavigationType,
replyUiState: ReplyUiState,
onTabPressed: (MailboxType) -> Unit = {},
onEmailCardPressed: (Email) -> Unit = {},
onDetailScreenBackPressed: () -> Unit = {},
modifier: Modifier = Modifier
) {
...
if (navigationType == ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER
&& replyUiState.isShowingHomepage
) {
}
if (replyUiState.isShowingHomepage) {
ReplyAppContent(
replyUiState = replyUiState,
...
- 永続ドロワーを作成するために、if 文の本体に
PermanentNavigationDrawer
コンポーザブルを作成し、drawerContent
パラメータの入力としてNavigationDrawerContent
コンポーザブルを追加します。 ReplyAppContent
コンポーザブルを、PermanentNavigationDrawer
の最後のラムダ引数として追加します。
ReplyHomeScreen.kt
...
if (navigationType == ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER
&& replyUiState.isShowingHomepage
) {
PermanentNavigationDrawer(
drawerContent = {
NavigationDrawerContent(
selectedDestination = replyUiState.currentMailbox,
onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList
)
}
) {
ReplyAppContent(
replyUiState = replyUiState,
onTabPressed = onTabPressed,
onEmailCardPressed = onEmailCardPressed,
navigationItemContentList = navigationItemContentList,
modifier = modifier
)
}
}
...
- 前のコンポーザブルの本体を使用する
else
分岐を追加し、拡大以外の画面には以前の分岐をそのまま使用します。
ReplyHomeScreen.kt
...
if (navigationType == ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER
&& replyUiState.isShowingHomepage
) {
PermanentNavigationDrawer(
drawerContent = {
NavigationDrawerContent(
selectedDestination = replyUiState.currentMailbox,
onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList
)
}
) {
ReplyAppContent(
replyUiState = replyUiState,
onTabPressed = onTabPressed,
onEmailCardPressed = onEmailCardPressed,
navigationItemContentList = navigationItemContentList,
modifier = modifier
)
}
} else {
if (replyUiState.isShowingHomepage) {
ReplyAppContent(
replyUiState = replyUiState,
onTabPressed = onTabPressed,
onEmailCardPressed = onEmailCardPressed,
navigationItemContentList = navigationItemContentList,
modifier = modifier
)
} else {
ReplyDetailsScreen(
replyUiState = replyUiState,
onBackPressed = onDetailScreenBackPressed,
modifier = modifier
)
}
}
}
...
ReplyHomeScreen
コンポーザブルに Experimental アノテーションを追加します。PermanentNavigationDrawer
API はまだ試験運用版であるため、このアノテーションが必要です。
ReplyHomeScreen.kt
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ReplyHomeScreen(
navigationType: ReplyNavigationType,
replyUiState: ReplyUiState,
onTabPressed: (MailboxType) -> Unit = {},
onEmailCardPressed: (Email) -> Unit = {},
onDetailScreenBackPressed: () -> Unit = {},
modifier: Modifier = Modifier
) {
...
- アプリをタブレット モードで実行します。下の画面が表示されます。
ナビゲーション レールを実装する
ナビゲーション ドロワーの実装と同様に、ナビゲーション要素間の切り替えを行うには navigationType
パラメータを使用する必要があります。
まず、中程度の画面用のナビゲーション レールを追加しましょう。
- はじめに、
ReplyAppContent
コンポーザブルにパラメータとしてnavigationType
を追加します。
ReplyHomeScreen.kt
...
@Composable
private fun ReplyAppContent(
navigationType: ReplyNavigationType,
replyUiState: ReplyUiState,
onTabPressed: ((MailboxType) -> Unit) = {},
onEmailCardPressed: (Email) -> Unit = {},
navigationItemContentList: List<NavigationItemContent>,
modifier: Modifier = Modifier
) {
...
- 両方の
ReplyAppContent
コンポーザブルにnavigationType
の値を渡します。
ReplyHomeScreen.kt
...
ReplyAppContent(
navigationType = navigationType,
replyUiState = replyUiState,
onTabPressed = onTabPressed,
onEmailCardPressed = onEmailCardPressed,
navigationItemContentList = navigationItemContentList,
modifier = modifier
)
}
} else {
if (replyUiState.isShowingHomepage) {
ReplyAppContent(
navigationType = navigationType,
replyUiState = replyUiState,
onTabPressed = onTabPressed,
onEmailCardPressed = onEmailCardPressed,
navigationItemContentList = navigationItemContentList,
modifier = modifier
)
...
次に、分岐を追加します。この分岐により、一部のシナリオでナビゲーション レールを表示できるようになります。
ReplyAppContent
コンポーザブル本体の 1 行目で、ReplyNavigationRail
コンポーザブルをAnimatedVisibility
コンポーザブルで囲み、ReplyNavigationType
の値がNavigationRail
の場合にvisibility
パラメータをtrue
に設定します。
ReplyHomeScreen.kt
...
@Composable
private fun ReplyAppContent(
navigationType: ReplyNavigationType,
replyUiState: ReplyUiState,
onTabPressed: ((MailboxType) -> Unit) = {},
onEmailCardPressed: (Email) -> Unit = {},
navigationItemContentList: List<NavigationItemContent>,
modifier: Modifier = Modifier
) {
AnimatedVisibility(visible = navigationType == ReplyNavigationType.NAVIGATION_RAIL) {
ReplyNavigationRail(
currentTab = replyUiState.currentMailbox,
onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList
)
}
Column(
modifier = Modifier
.fillMaxSize() .background(MaterialTheme.colorScheme.inverseOnSurface)
) {
ReplyListOnlyContent(
replyUiState = replyUiState,
onEmailCardPressed = onEmailCardPressed,
modifier = Modifier.weight(1f)
)
ReplyBottomNavigationBar(
currentTab = replyUiState.currentMailbox,
onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList
)
}
}
...
- コンポーザブルを正しく配置するために、
ReplyAppContent
の本体にあるAnimatedVisibility
コンポーザブルとColumn
コンポーザブルの両方をRow
コンポーザブルで囲みます。
ReplyHomeScreen.kt
...
@Composable
private fun ReplyAppContent(
navigationType: ReplyNavigationType,
replyUiState: ReplyUiState,
onTabPressed: ((MailboxType) -> Unit) = {},
onEmailCardPressed: (Email) -> Unit = {},
navigationItemContentList: List<NavigationItemContent>,
modifier: Modifier = Modifier
) {
Row(modifier = modifier.fillMaxSize()) {
AnimatedVisibility(visible = navigationType == ReplyNavigationType.NAVIGATION_RAIL) {
ReplyNavigationRail(
currentTab = replyUiState.currentMailbox,
onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList
)
}
Column(
modifier = Modifier
.fillMaxSize() .background(MaterialTheme.colorScheme.inverseOnSurface)
) {
ReplyListOnlyContent(
replyUiState = replyUiState,
onEmailCardPressed = onEmailCardPressed,
modifier = Modifier.weight(1f)
)
ReplyBottomNavigationBar(
currentTab = replyUiState.currentMailbox,
onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList
)
}
}
}
...
最後に、一部のシナリオでボトム ナビゲーションが表示されることを確認しましょう。
ReplyListOnlyContent
コンポーザブルの後で、ReplyBottomNavigationBar
コンポーザブルをAnimatedVisibility
コンポーザブルで囲みます。ReplyNavigationType
の値がBOTTOM_NAVIGATION
の場合、visible
パラメータを設定します。
ReplyHomeScreen.kt
...
ReplyListOnlyContent(
replyUiState = replyUiState,
onEmailCardPressed = onEmailCardPressed,
modifier = Modifier.weight(1f)
)
AnimatedVisibility(visible = navigationType == ReplyNavigationType.BOTTOM_NAVIGATION) {
ReplyBottomNavigationBar(
currentTab = replyUiState.currentMailbox,
onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList
)
}
...
- アプリを開いた状態の折りたたみ式デバイスで実行します。下の画面が表示されます。
8. 解答コードを取得する
この Codelab の完成したコードをダウンロードするには、次の git コマンドを使用します。
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-reply-app.git cd basic-android-kotlin-compose-training-reply-app git checkout nav-update
または、リポジトリを ZIP ファイルとしてダウンロードし、Android Studio で開くこともできます。
解答コードを確認する場合は、GitHub で表示します。
9. おわりに
お疲れさまでした。アダプティブ ナビゲーション レイアウトを実装したことで、Reply アプリをあらゆる画面サイズに適応させることに一歩近づきました。また、さまざまな Android フォーム ファクタを使用するユーザー エクスペリエンスを向上させました。次の Codelab では、アダプティブ コンテンツ レイアウトの実装、テスト、プレビューを行い、アダプティブ アプリを扱うスキルをさらに高めます。
作成したら、#AndroidBasics を付けて、ソーシャル メディアで共有しましょう。