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 を受講することを検討してください。

作成するアプリの概要

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

この 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 つの形状になります。

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

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

適応型情報を取得する

マテリアル 3 の 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 つのペインを表示します。それ以外の場合は、scaffold ディレクティブと scaffold 値の 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 で説明したベスト プラクティスを組み込んだ多くのアプリのコレクションです。

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