Jetpack Compose でアダプティブ アプリを作成する

1. はじめに

この Codelab では、スマートフォン、タブレット、折りたたみ式デバイスに対応したアダプティブ アプリを作成する方法と、Jetpack Compose で到達性を高める方法を学びます。また、マテリアル 3 のコンポーネントの使用とテーマ設定に関するベスト プラクティスについても学習します。

詳細に入る前に、適応性とは何かを理解しましょう。

適応性

アプリの UI は、さまざまなウィンドウ サイズ、向き、フォーム ファクタに対応するためにレスポンシブでなければなりません。アダプティブ レイアウトは、使用可能な画面スペースに応じて変化します。この変化は、スペースを埋めるための単純なレイアウト調整から、それぞれのナビゲーション スタイルの選択、追加の空間を活用するためのレイアウトの完全な変更まで、多岐にわたります。

詳しくは、アダプティブ デザインをご覧ください。

この Codelab では、Jetpack Compose を使用する際の適応性についての考え方と使い方を学びます。Reply という、あらゆる種類の画面で適応性を実現する方法と、適応性と到達性を組み合わせて、最適なユーザー エクスペリエンスを提供する方法について説明するアプリを作成します。

学習内容

  • Jetpack Compose ですべてのウィンドウ サイズに対応するアプリを設計する方法
  • アプリをさまざまな折りたたみ式デバイスに対応させる方法
  • さまざまなナビゲーションを使って到達性とアクセシビリティを向上させる方法
  • マテリアル 3 コンポーネントを使って、あらゆるウィンドウ サイズで最適なエクスペリエンスを実現する方法

必要なもの

  • Android Studio の最新の安定版。
  • Android 13 のサイズ変更可能な仮想デバイス
  • Kotlin に関する知識
  • Compose に関する基礎知識(@Composable アノテーションなど)
  • Compose レイアウト(RowColumn など)に関する基本的な知識。
  • 修飾子(Modifier.padding() など)に関する基本的な知識。

この Codelab では、サイズ変更可能なエミュレータを使用します。このエミュレータでは、さまざまな種類のデバイスとウィンドウ サイズを切り替えることができます。

スマートフォン、広げた状態の折りたたみ式デバイス、タブレット、デスクトップのオプションを備えた、サイズ変更可能なエミュレータ

Compose に不慣れな場合は、この Codelab の前に、Jetpack Compose の基本の Codelab を受講することを検討してください。

作成するアプリの概要

  • 自動適応するデザイン、各種マテリアル ナビゲーション、最適な画面スペース使用に関するベスト プラクティスを採用したインタラクティブなメール クライアント アプリ「Reply」

この Codelab で作成する複数デバイス サポートの紹介

2. セットアップする

この Codelab のコードを入手するには、コマンドラインから GitHub リポジトリのクローンを作成します。

git clone https://github.com/android/codelab-android-compose.git
cd codelab-android-compose/AdaptiveUiCodelab

または、リポジトリを ZIP ファイルとしてダウンロードすることもできます。

main ブランチのコードから始め、ご自身のペースで Codelab を進めることをおすすめします。

Android Studio でプロジェクトを開く

  1. [Welcome to Android Studio] ウィンドウで、c01826594f360d94.png [Open an Existing Project] を選択します。
  2. <Download Location>/AdaptiveUiCodelab フォルダを選択します(build.gradle が格納されている AdaptiveUiCodelab ディレクトリを選択してください)。
  3. Android Studio にプロジェクトがインポートされたら、main ブランチを実行できるかどうかをテストします。

start コードを確認する

main ブランチのコードには、ui パッケージが含まれています。そのパッケージ内の次のファイルを使用します。

  • MainActivity.kt - アプリの出発点となるエントリ ポイント アクティビティ。
  • ReplyApp.kt - メイン画面の UI コンポーザブルがあります。
  • ReplyHomeViewModel.kt - アプリ コンテンツのデータと UI 状態を提供します。
  • ReplyListContent.kt - リストと詳細画面を提供するためのコンポーザブルがあります。

このアプリをサイズ変更可能なエミュレータで実行し、スマートフォンやタブレットなどのさまざまなデバイスタイプを試しても、UI は与えられたスペースに広がるだけで、画面スペースを使用したり、到達性に関するエルゴノミクスを提供したりすることはありません。

スマートフォンの初期画面

タブレットの引き伸ばされた初期ビュー

これを更新して、画面スペースを有効活用し、ユーザビリティを高め、全体的なユーザー エクスペリエンスを向上させます。

3. アプリを自動適応にする

このセクションでは、アプリを自動適応にする手段と、それを簡単にするためにマテリアル 3 に用意されているコンポーネントについて説明します。また、スマートフォン、タブレット、大型タブレット、折りたたみ式デバイスなど、対応する画面の種類と状態についても説明します。

まず、ウィンドウ サイズ、折りたたみ形状、さまざまなナビゲーション オプションの基本について説明します。これによって、これらの API をアプリ内で使用して、アプリの適応性を高めることが可能になります。

ウィンドウ サイズ

Android デバイスには、スマートフォンから折りたたみ式デバイス、タブレット、ChromeOS デバイスまで、あらゆる形とサイズがあります。できるだけ多くのウィンドウ サイズをサポートするには、UI をレスポンシブかつアダプティブにする必要があります。アプリの UI を変更する適切なしきい値を見つけられるよう、デバイスをウィンドウ サイズクラスという事前定義されたサイズのクラス(コンパクト、中程度、拡大)に分類するブレークポイント値を定義しました。ウィンドウ サイズクラスは、アプリのレスポンシブ レイアウト、アダプティブ レイアウトを設計、開発、テストする際に役立つ独自のビューポート ブレークポイントのセットです。

これらのカテゴリは、柔軟性とレイアウトのシンプルさのバランスを取りつつ、固有のケースに合わせてアプリを最適化できるように、特別に選択されたものです。ウィンドウ サイズクラスは、アプリが利用可能な画面スペースによって常に決まり、マルチタスクや他の分割が行われている場合は、物理画面全体ではない場合があります。

コンパクト、中程度、拡大の幅の WindowWidthSizeClass。

コンパクト、中程度、拡大の高さ用の WindowHeightSizeClass

幅と高さの両方は個別に分類されるため、どの時点でも、アプリにはウィンドウ サイズクラスが 2 つ(幅用と高さ用)あります。縦スクロールが一般的であることから、通常は利用可能な幅の方が利用可能な高さより重要です。したがって、この場合も幅のサイズクラスを使用します。

折りたたみ状態

折りたたみ式デバイスは、サイズが異なり、ヒンジがあるため、アプリが対応できる状況がさらに増えます。ヒンジはディスプレイの一部を隠すことがあり、その領域はコンテンツの表示に適していません。また、ヒンジは分離している場合もあります。つまり、デバイスを開いたときに 2 つの別々の物理ディスプレイが存在します。

折りたたみ式デバイスの形状(平坦と半開き)

また、ヒンジが部分的に開いた状態で内側のディスプレイを見ている場合もあります。この場合、折り目の向きに応じて、テーブルトップ形状(折り目が水平方向、上の画像の右側)とブック形状(折り目が垂直方向)の 2 つの形状になります。

詳しくは、折りたたみ形状とヒンジに関するドキュメントをご覧ください。

これらはすべて、折りたたみ式デバイスをサポートするアダプティブ レイアウトを実装する際に考慮すべき事項です。

適応型情報を取得する

Material3 の adaptive ライブラリを使用すると、アプリが実行されているウィンドウに関する情報に簡単にアクセスできます。

  1. このアーティファクトとそのバージョンのエントリをバージョン カタログ ファイルに追加します。

gradle/libs.versions.toml

[versions]
material3Adaptive = "1.0.0"

[libraries]
androidx-material3-adaptive = { module = "androidx.compose.material3.adaptive:adaptive", version.ref = "material3Adaptive" }
  1. アプリ モジュールのビルドファイルに新しいライブラリの依存関係を追加し、Gradle 同期を行います。

app/build.gradle.kts

dependencies {

    implementation(libs.androidx.material3.adaptive)
}

これで、コンポーザブル スコープで currentWindowAdaptiveInfo() を使用して、現在のウィンドウ サイズクラスや、デバイスがテーブルトップなどの折りたたみ式形状になっているかどうかなどの情報を含む WindowAdaptiveInfo オブジェクトを取得できます。

この機能は MainActivity でお試しいただけます。

  1. ReplyTheme ブロック内の onCreate() で、ウィンドウの適応情報を取得し、Text コンポーザブルでサイズクラスを表示します。これは ReplyApp() 要素の後に次のように追加できます。

MainActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContent {
        ReplyTheme {
            val uiState by viewModel.uiState.collectAsStateWithLifecycle()
            ReplyApp(
                replyHomeUIState = uiState,
                onEmailClick = viewModel::setSelectedEmail
            )

            val adaptiveInfo = currentWindowAdaptiveInfo()
            val sizeClassText =
                "${adaptiveInfo.windowSizeClass.windowWidthSizeClass}\n" +
                "${adaptiveInfo.windowSizeClass.windowHeightSizeClass}"
            Text(
                text = sizeClassText,
                color = Color.Magenta,
                modifier = Modifier.padding(
                    WindowInsets.safeDrawing.asPaddingValues()
                )
            )
        }
    }
}

アプリを実行すると、アプリのコンテンツの上にウィンドウ サイズクラスが表示されます。ウィンドウ適応情報で他に何が提供されているかを確認してください。その後、アプリのコンテンツをカバーしているため、この Text を削除できます。次のステップでは必要ありません。

4. ダイナミック ナビゲーション

ここでは、デバイスの状態とサイズの変化に応じてアプリのナビゲーションを調整し、アプリを使いやすくします。

スマートフォンを持つとき、通常は指が画面の下部にあります。ユーザーが折りたたみ式デバイスやタブレットを開いて手に持ったとき、通常、指は側面に近い位置にあります。ユーザーは、アプリを操作するために極端な持ち方をしたり、持ち替えたりすることなく、アプリを操作したり、アプリとのやり取りを開始したりできる必要があります。

アプリを設計し、レイアウト内のインタラクティブな UI 要素の配置場所を決定する際は、画面のさまざまな領域の人間工学的な影響を考慮してください。

  • デバイスを持っているとき、どの範囲に手が届きやすいですか?
  • 指を伸ばさないと届かないため、やや不自然な動作になる範囲はどこですか?
  • デバイスを保持している場所から手が届きにくい場所や遠い場所はどこですか?

ナビゲーションはユーザーが最初に操作するものであり、クリティカル ユーザー ジャーニーに関連する重要なアクションが含まれているため、最もアクセスしやすい場所に配置する必要があります。マテリアル アダプティブ ライブラリには、デバイスのウィンドウ サイズクラスに応じて、ナビゲーションの実装に役立つコンポーネントがいくつか用意されています。

ボトム ナビゲーション

ボトム ナビゲーションは、自然な持ち方をした場合に親指がすべてのタッチポイントに簡単に届くので、コンパクト サイズに最適です。デバイスサイズがコンパクトの場合や、折りたたみ式デバイスの折りたたみ状態がコンパクトである場合に使用します。

アイテムがあるボトム ナビゲーション バー

中程度の幅のウィンドウ サイズでは、親指が自然にデバイスの側面に置かれるため、ナビゲーション レールが到達性に優れています。ナビゲーション レールとナビゲーション ドロワーを組み合わせて、詳細情報を表示することもできます。

アイテムがあるナビゲーション レール

ナビゲーション ドロワーを使用すると、ナビゲーション タブの詳細情報を簡単に確認できます。また、ナビゲーション ドロワーは、タブレットや大型デバイスを使用している場合に簡単にアクセスできます。ナビゲーション ドロワーには、モーダル ナビゲーション ドロワーと固定的なナビゲーション ドロワーの 2 種類があります。

モーダル ナビゲーション ドロワー

コンパクトから中程度までのサイズのスマートフォンとタブレットには、コンテンツ上でオーバーレイとして展開したり、非表示にしたりできるので、モーダル ナビゲーション ドロワーも使用できます。ナビゲーション レールと組み合わせることもできます。

アイテムがあるモーダル ナビゲーション ドロワー

固定的なナビゲーション ドロワー

大型のタブレット、Chromebook、デスクトップでは、固定ナビゲーションに固定的なナビゲーション ドロワーを使用できます。

アイテムがある固定的なナビゲーション ドロワー

ダイナミック ナビゲーションを実装する

それでは、デバイスの状態とサイズの変化に応じてナビゲーションを切り替えましょう。

現在、アプリはデバイスの状態に関わらず、常に画面コンテンツの下に NavigationBar を表示します。代わりに、マテリアルの NavigationSuiteScaffold コンポーネントを使用すると、現在のウィンドウ サイズクラスなどの情報に基づいて、さまざまなナビゲーション コンポーネントを自動的に切り替えることができます。

  1. バージョン カタログとアプリのビルド スクリプトを更新してこのコンポーネントを取得する Gradle 依存関係を追加し、Gradle 同期を実行します。

gradle/libs.versions.toml

[versions]
material3AdaptiveNavSuite = "1.3.0"

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

app/build.gradle.kts

dependencies {

    implementation(libs.androidx.material3.adaptive.navigation.suite)
}
  1. ReplyApp.kt でコンポーズ可能な関数 ReplyNavigationWrapper() を見つけ、Column とそのコンテンツを NavigationSuiteScaffold に置き換えます。

ReplyApp.kt

@Composable
private fun ReplyNavigationWrapperUI(
    content: @Composable () -> Unit = {}
) {
    var selectedDestination: ReplyDestination by remember {
        mutableStateOf(ReplyDestination.Inbox)
    }

    NavigationSuiteScaffold(
        navigationSuiteItems = {
            ReplyDestination.entries.forEach {
                item(
                    selected = it == selectedDestination,
                    onClick = { /*TODO update selection*/ },
                    icon = {
                        Icon(
                            imageVector = it.icon,
                            contentDescription = stringResource(it.labelRes)
                        )
                    },
                    label = {
                        Text(text = stringResource(it.labelRes))
                    },
                )
            }
        }
    ) {
        content()
    }
}

navigationSuiteItems 引数は、LazyColumn でアイテムを追加するのと同様に、item() 関数を使用してアイテムを追加できるブロックです。後置ラムダ内で、このコードは ReplyNavigationWrapperUI() の引数として渡された content() を呼び出します。

エミュレータでアプリを実行し、スマートフォン、折りたたみ式、タブレットの間でサイズを変更してみると、ナビゲーション バーがナビゲーション レールに切り替わったり、元に戻ったりするのがわかります。

横向きのタブレットなど、非常に幅の広いウィンドウでは、固定的なナビゲーション ドロワーを表示することが望ましい場合があります。NavigationSuiteScaffold は永続的なドロワーの表示をサポートしていますが、現在の WindowWidthSizeClass 値には表示されません。ただし、少し変更すれば、そうすることができます。

  1. NavigationSuiteScaffold の呼び出しの直前に次のコードを追加します。

ReplyApp.kt

@Composable
private fun ReplyNavigationWrapperUI(
    content: @Composable () -> Unit = {}
) {
    var selectedDestination: ReplyDestination by remember {
        mutableStateOf(ReplyDestination.Inbox)
    }

    val windowSize = with(LocalDensity.current) {
        currentWindowSize().toSize().toDpSize()
    }
    val layoutType = if (windowSize.width >= 1200.dp) {
        NavigationSuiteType.NavigationDrawer
    } else {
        NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(
            currentWindowAdaptiveInfo()
        )
    }

    NavigationSuiteScaffold(
        layoutType = layoutType,
        ...
    ) {
        content()
    }
}

このコードでは、まずウィンドウ サイズを取得し、currentWindowSize()LocalDensity.current を使用して DP 単位に変換してから、ウィンドウの幅を比較してナビゲーション UI のレイアウト タイプを決定します。ウィンドウの幅が 1200.dp 以上の場合は、NavigationSuiteType.NavigationDrawer を使用します。それ以外の場合は、デフォルトの計算に戻ります。

サイズ変更可能なエミュレータでアプリを再度実行し、さまざまなタイプを試すと、画面構成が変更されたり、折りたたみ式デバイスを開いたりするたびに、ナビゲーションがそのサイズに適したタイプに変わるのがわかります。

デバイスのサイズに応じた適応性の変化を示している。

ここまでで、さまざまなウィンドウ サイズと状態をサポートする各種のナビゲーションについて学習しました。

次のセクションでは、同じリスト項目を端から端まで引き伸ばさずに、残りの画面領域を活用する方法を説明します。

5. 画面スペースの使用

アプリを実行しているのが小さなタブレット、広げたデバイス、大型タブレットのどれであっても、画面は引き伸ばされ、残りのスペースが使われます。このアプリのように、その画面スペースを活用して、同じページにメールとスレッドを表示するなど、より多くの情報を表示できるようにしてはどうでしょうか。

マテリアル 3 では、コンパクト、中程度、拡大のウィンドウ サイズクラスごとに構成が用意された 3 つの正規レイアウトが定義されています。このユースケースには、リスト詳細の正規レイアウトが最適です。このレイアウトは、Compose では ListDetailPaneScaffold として利用できます。

  1. このコンポーネントを取得するには、次の依存関係を追加して Gradle 同期を実行します。

gradle/libs.versions.toml

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

app/build.gradle.kts

dependencies {

    implementation(libs.androidx.material3.adaptive.layout)
    implementation(libs.androidx.material3.adaptive.navigation)
}
  1. ReplyApp.kt でコンポーズ可能な関数 ReplyAppContent() を見つけます。この関数は現在、ReplyListPane() を呼び出すことでリストペインのみを表示しています。次のコードを挿入して、この実装を ListDetailPaneScaffold に置き換えます。これは試験運用版の API であるため、ReplyAppContent() 関数に @OptIn アノテーションも追加します。

ReplyApp.kt

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ReplyAppContent(
    replyHomeUIState: ReplyHomeUIState,
    onEmailClick: (Email) -> Unit,
) {
    val navigator = rememberListDetailPaneScaffoldNavigator<Long>()

    ListDetailPaneScaffold(
        directive = navigator.scaffoldDirective,
        value = navigator.scaffoldValue,
        listPane = {
            ReplyListPane(replyHomeUIState, onEmailClick)
        },
        detailPane = {
            ReplyDetailPane(replyHomeUIState.emails.first())
        }
    )
}

このコードは、まず rememberListDetailPaneNavigator() を使用してナビゲーターを作成します。ナビゲーターでは、どのペインを表示するか、そのペインにどのようなコンテンツを表示するかを制御できます。これについては後で説明します。

ウィンドウの幅のサイズクラスが拡大されている場合、ListDetailPaneScaffold は 2 つのペインを表示します。それ以外の場合は、スキャフォールディング ディレクティブとスキャフォールディング値の 2 つのパラメータに指定された値に基づいて、いずれかのペインが表示されます。デフォルトの動作を取得するために、このコードではスキャフォールド ディレクティブとナビゲーターから提供されたスキャフォールド値を使用します。

残りの必須パラメータは、ペインのコンポーザブル ラムダです。ReplyListPane()ReplyDetailPane()ReplyListContent.kt にあります)は、それぞれリストペインと詳細ペインのロールを埋めるために使用されます。ReplyDetailPane() はメール引数を想定しているため、このコードでは、今のところ ReplyHomeUIState のメールリストの最初のメールを使用しています。

アプリを実行し、エミュレータのビューを折りたたみ式またはタブレットに切り替えて(向きを変更する必要がある場合もあります)、2 ペイン レイアウトを確認します。以前よりずっと見やすくなりました。

次に、この画面の望ましい動作について説明します。ユーザーがリストペインでメールをタップすると、そのメールとすべての返信が詳細ペインに表示されます。現在、アプリはどのメールが選択されたかを追跡しておらず、アイテムをタップしても何も起こりません。この情報を保存するのに最適な場所は、ReplyHomeUIState の他の UI 状態とともに保存することです。

  1. ReplyHomeViewModel.kt を開き、ReplyHomeUIState データクラスを見つけます。選択したメールのプロパティを追加します。デフォルト値は null です。

ReplyHomeViewModel.kt

data class ReplyHomeUIState(
    val emails : List<Email> = emptyList(),
    val selectedEmail: Email? = null,
    val loading: Boolean = false,
    val error: String? = null
)
  1. 同じファイルで、ReplyHomeViewModel には、ユーザーがリスト項目をタップしたときに呼び出される setSelectedEmail() 関数があります。この関数を変更して、UI 状態をコピーし、選択したメールを記録します。

ReplyHomeViewModel.kt

fun setSelectedEmail(email: Email) {
    _uiState.update {
        it.copy(selectedEmail = email)
    }
}

考慮すべき点は、ユーザーがアイテムをタップする前、つまり選択されたメールが null の場合です。詳細ペインに表示する内容このケースに対処する方法は複数あります(リストの最初の項目をデフォルトで表示するなど)。

  1. 同じファイルで、observeEmails() 関数を変更します。メールのリストが読み込まれたときに、前の UI 状態に選択されたメールがない場合は、最初のアイテムに設定します。

ReplyHomeViewModel.kt

private fun observeEmails() {
    viewModelScope.launch {
        emailsRepository.getAllEmails()
            .catch { ex ->
                _uiState.value = ReplyHomeUIState(error = ex.message)
            }
            .collect { emails ->
                val currentSelection = _uiState.value.selectedEmail
                _uiState.value = ReplyHomeUIState(
                    emails = emails,
                    selectedEmail = currentSelection ?: emails.first()
                )
            }
    }
}
  1. ReplyApp.kt に戻り、選択したメールが利用可能な場合は、それを使用して詳細ペインのコンテンツを入力します。

ReplyApp.kt

ListDetailPaneScaffold(
    // ...
    detailPane = {
        if (replyHomeUIState.selectedEmail != null) {
            ReplyDetailPane(replyHomeUIState.selectedEmail)
        }
    }
)

アプリを再度実行し、エミュレータをタブレット サイズに切り替えます。リストアイテムをタップすると、詳細ペインの内容が更新されます。

両方のペインが表示されている場合は問題ありませんが、ウィンドウに 1 つのペインしか表示できない場合、アイテムをタップしても何も起こらないように見えます。エミュレータのビューをスマートフォンまたは縦向きの折りたたみ式デバイスに切り替えて、項目をタップしてもリストペインのみが表示されることを確認します。これは、選択したメールは更新されますが、ListDetailPaneScaffold はこれらの構成でリストペインにフォーカスを維持しているためです。

  1. これを修正するには、次のコードを ReplyListPane に渡されるラムダとして挿入します。

ReplyApp.kt

ListDetailPaneScaffold(
    // ...
    listPane = {
        ReplyListPane(
            replyHomeUIState = replyHomeUIState,
            onEmailClick = { email ->
                onEmailClick(email)
                navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, email.id)
            }
        )
    },
    // ...
)

このラムダは、前に作成したナビゲータを使用して、アイテムがクリックされたときの動作を追加します。この関数に渡された元のラムダを呼び出し、表示するペインを指定する navigator.navigateTo() も呼び出します。スキャフォールドの各ペインにはロールが関連付けられており、詳細ペインの場合は ListDetailPaneScaffoldRole.Detail です。小さいウィンドウでは、アプリが前方に移動したように見えます。

アプリは、ユーザーが詳細ペインから戻るボタンを押したときの処理も行う必要があります。この動作は、表示されているペインが 1 つか 2 つかによって異なります。

  1. 次のコードを追加して、戻るナビゲーションをサポートします。

ReplyApp.kt

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ReplyAppContent(
    replyHomeUIState: ReplyHomeUIState,
    onEmailClick: (Email) -> Unit,
) {
    val navigator = rememberListDetailPaneScaffoldNavigator<Long>()

    BackHandler(navigator.canNavigateBack()) {
        navigator.navigateBack()
    }

    ListDetailPaneScaffold(
        directive = navigator.scaffoldDirective,
        value = navigator.scaffoldValue,
        listPane = {
            AnimatedPane {
                ReplyListPane(
                    replyHomeUIState = replyHomeUIState,
                    onEmailClick = { email ->
                        onEmailClick(email)
                        navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, email.id)
                    }
                )
            }
        },
        detailPane = {
            AnimatedPane {
                if (replyHomeUIState.selectedEmail != null) {
                    ReplyDetailPane(replyHomeUIState.selectedEmail)
                }
            }
        }
    )
}

ナビゲーターは、ListDetailPaneScaffold の完全な状態、戻るナビゲーションが可能かどうか、これらのすべてのシナリオで何をすべきかを把握しています。このコードは、ナビゲータが戻るたびに有効になる BackHandler を作成し、ラムダ内で navigateBack() を呼び出します。また、ペイン間の移行をよりスムーズにするため、各ペインは AnimatedPane() コンポーザブルでラップされています。

サイズ変更可能なエミュレータを使い、さまざまなタイプのデバイスでアプリを再度実行すると、画面構成が変更されるか、折りたたみ式デバイスが開かれたとき、デバイス状態の変化に応答してナビゲーションと画面コンテンツが動的に変化することがわかります。また、リストペインでメールをタップして、さまざまな画面でレイアウトがどのように動作するかを確認してください。両方のペインが横に並んで表示されたり、ペイン間がスムーズにアニメーションで切り替わったりします。

デバイスのサイズに応じた適応性の変化を示している。

これで、アプリをあらゆる種類のデバイスとサイズに自動適応させることができました。折りたたみ式デバイス、タブレット、その他のモバイル デバイスでアプリを実行してみましょう。

6. 完了

おめでとうございます!これで、この Codelab は終了です。Jetpack Compose でアプリをアダプティブにする方法を学びました。

デバイスのサイズと折りたたみ状態をチェックし、それに応じてアプリの UI、ナビゲーション、その他の機能を更新する方法を学習しました。また、適応性によって到達性が向上し、ユーザー エクスペリエンスが強化されることも学びました。

次のステップ

Compose パスウェイに関する他の Codelab をご確認ください。

サンプルアプリ

  • Compose サンプルは、Codelab で説明されているベスト プラクティスを取り入れた多数のアプリを集めたものです。

リファレンス ドキュメント