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

1. はじめに

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

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

適応性

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

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

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

学習内容

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

必要なもの

この 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 - リストと詳細画面を提供するためのコンポーザブルがあります。

まずは MainActivity.kt に着目します。

MainActivity.kt

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

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

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

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

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

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

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

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

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

ウィンドウ サイズ

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

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

コンパクト、中程度、拡大の各幅用の WindowWidthSizeClass の分類

コンパクト、中程度、拡大用の WindowHeightSizeClass の分類

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

折りたたみ状態

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

折りたたみ形状(フラット、半開き)

また、ヒンジが部分的に開いている状態でインナー ディスプレイを見ると、折りたたみの向きに応じてテーブルトップの形状(上の画像の右側に示される水平の折り目)とブックの形状(縦の折り目)で物理的な形状が異なる可能性があります。

詳しくは、折りたたみ形状とヒンジをご覧ください。

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

適応型の情報を取得する

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

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

gradle/libs.versions.toml

[versions]
material3Adaptive = "1.0.0-beta01"

[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(20.dp)
            )
        }
    }
}

アプリを実行すると、アプリのコンテンツに出力されたウィンドウ サイズクラスが表示されます。ウィンドウ アダプティブ情報に表示されるその他の情報もご検討ください。この Text はアプリのコンテンツを覆っているため、次のステップでは不要なため、後で削除できます。

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

次は、到達性を高めるために、デバイスの状態とサイズの変化に合わせてアプリのナビゲーションを調整します。

到達性とは、極端な持ち方をしたり、持ち替えたりすることなく、アプリを操作したり操作を開始したりできることを指します。スマートフォンを持っているとき、指は通常、画面の下部にあります。開いた状態の折りたたみ式デバイスやタブレットを持つとき、指は通常、側面の近くにあります。アプリを設計し、インタラクティブな UI 要素をレイアウト内のどこに配置するかを決定する際には、画面のさまざまな領域が人間工学的にどのような意味を持つかを考慮してください。

  • デバイスを持っているときに手が届くのはどのような場所ですか?
  • 指を伸ばすだけで到達できて不便と思われる分野はどれですか?
  • 手の届かないところや、デバイスを持つ場所から遠くにあるところはどこですか?

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

ボトム ナビゲーション

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

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

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

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

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

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

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

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

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

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

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

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

次に、デバイスの状態とサイズの変化に応じて、ナビゲーションの種類を切り替えます。

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

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

gradle/libs.versions.toml

[versions]
material3AdaptiveNavSuite = "1.3.0-beta01"

[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

[versions]
material3Adaptive = "1.0.0-beta01"

[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 つのパラメータに指定された値に基づいて、一方のペインまたはもう一方のペインを表示します。デフォルトの動作を実現するために、このコードでは scaffold ディレクティブと、ナビゲータによって提供される scaffold 値を使用します。

残りの必須パラメータは、ペインのコンポーズ可能なラムダです。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 の完全な状態、戻るナビゲーションの可否、これらすべてのシナリオでの対処方法を認識しています。このコードは、ナビゲータが戻ることができ、ラムダ内で navigateBack() を呼び出すたびに有効になる BackHandler を作成します。また、ペイン間の遷移をよりスムーズにするために、各ペインを AnimatedPane() コンポーザブルでラップします。

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

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

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

6. 完了

お疲れさまでした。この Codelab は終了です。Jetpack Compose でアプリをアダプティブにする方法を学びました。

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

次のステップ

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

サンプルアプリ

  • Compose サンプルは、Codelab で説明したベスト プラクティスを組み込んだ多くのアプリのコレクションです。

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