UI をシステム ステータスバーと連携させる

Wear OS 7(API 37)までは、プラットフォームはアプリ ウィンドウの上にシステム ステータスバーをレンダリングしませんでした。アプリが画面全体を所有し、上部(12 時)の位置に任意のコンテンツ(アプリ内 TimeText 時計、カスタムの曲線ヘッダー、上部コントロールなど)を描画できました。

Wear OS 8 以降(Wear OS 7.1 ベータ版プレビュー エミュレータ(API 37.2-beta1)で早期プレビュー テストが可能)では、システムはアプリ ウィンドウの 12 時の位置にステータスバーをレンダリングし、時刻とステータス インジケーターを表示します。システム ステータスバーとの重複を防ぐため、アプリは独自の時計(TimeText)のレンダリングを停止し、上部のステータスバー ウィンドウ インセットを尊重する必要があります(Wear Compose 1.8 以降では AppScaffold と ScreenScaffold によって自動的に処理されます)。または、全画面表示の没入型エクスペリエンスが必要な場合は、システム ステータスバーを非表示にします。

Wear OS 8 のシステム ステータスバー
図 1. Wear OS 8 のシステム ステータスバー。

主なメリットは次のとおりです。

  • 視覚的な一貫性: システムとすべてのアプリで、時計のタイポグラフィ、配置、バックプレートが統一されています。
  • システム ステータス インジケーター: プラットフォームが、ステータス アイコン(アクティブなマイクや位置情報のインジケーター、接続性ステータス、バッテリー情報など)と時間をサーフェス全体に表示できるようにします。優先度に基づいて、一度に 1 つのアイコンのみが表示されます。
  • 自動スクロールと遷移の調整: Jetpack Compose for Wear OS 1.8 と組み合わせると、ユーザーがコンテンツをスクロールしたときにシステム ステータスバーが自動的に非表示になり、短い(2 秒)アイドル遅延の後に再表示されます。

アプリを設定する

まず、次の前提条件の手順を完了します。

  1. Android Studio プレビュー(Canary)をインストールします。
  2. SDK Manager で、Wear OS 7.1 プレビュー エミュレータ システム イメージ(API 37.2-beta1)をダウンロードします。このバージョンでは、Wear OS 8 SDK に先駆けて Wear OS 8 システム ステータスバーに早期アクセスできます。
  3. デバイス マネージャーで、API 37.2-beta1 イメージを使用して Wear OS 仮想デバイスを作成して起動します。

アプリでシステム ステータスバーをサポートするには、アプリの targetSdk バージョンを 35 以降に更新します。以前のアプリの下位互換性を維持するため、プラットフォームは API 35 以降をターゲットとするアプリに対してのみシステム ステータスバーを有効にします。また、次のコード スニペットに示すように、Wear Compose の依存関係を最新のアルファ版(現在は 1.8.0-alpha03)に更新します。アプリで古いバージョンの Wear Compose を使用している場合は、Wear Compose Material 3 移行スキルを使用して移行を容易にできます。

android {
    defaultConfig {
        targetSdk = 35
    }
}

dependencies {
    implementation("androidx.wear.compose:compose-foundation:1.8.0-alpha03")
    implementation("androidx.wear.compose:compose-material3:1.8.0-alpha03")
}

システム ステータスバーを使用する

Wear Compose Material 3 1.8.0 の最新のアルファ版(現在は 1.8.0-alpha03)では、ライブラリが com.google.wear.ENABLE_GLOBAL_STATUS_BAR <meta-data> プロパティを AndroidManifest.xml にマージすることで、アプリを自動的にオプトインします。実行時に、AppScaffold と ScreenScaffold は、デバイスでシステムのステータスバーが有効になっているかどうか、また、このマニフェストのオプトイン プロパティが true に設定されているかどうかを確認します。

  • プレビュー エミュレータ / Wear OS 8 以降(オプトイン時): AppScaffold は内部の TimeText を抑制するため、システム ステータスバーは 12 時の位置に重複なくきれいにレンダリングされます。ScreenScaffold は、ステータスバーのインセット境界を考慮して contentPadding を再計算し、リストのスクロールに合わせてステータスバーの表示と非表示を調整します。
  • Wear OS 7 以前のデバイス(またはオプトインしていない場合): AppScaffold は、自動的に標準のアプリ内 TimeText のレンダリングにフォールバックし、単一のコードベースから完全な下位互換性を維持します。

@Composable
fun GsbSampleApp() {
    // AppScaffold coordinates the system status bar across the app
    // and falls back to rendering TimeText on Wear OS 7 and earlier.
    AppScaffold {
        val columnState = rememberTransformingLazyColumnState()

        // ScreenScaffold includes status bar top inset clearance in
        // contentPadding and hides/shows the status bar during list scrolling.
        ScreenScaffold(scrollState = columnState) { contentPadding ->
            TransformingLazyColumn(
                state = columnState,
                contentPadding = contentPadding,
            ) {
                item {
                    ListHeader { Text("Hello Status Bar") }
                }
                items(count = 15) { index ->
                    Button(onClick = { /* Handle click */ }) {
                        Text("Item #${index + 1}")
                    }
                }
            }
        }
    }
}

サンプルを実行すると、次のようになります。

  • 静止時(リストの一番上): システム ステータスバーが 12 時の位置に表示されます。「Hello Status Bar」ヘッダーは、クリッピングや重複なしでステータスバーのインセット領域の下に配置されます。
  • 下方向にスクロール: リストアイテムをスクロールすると、ステータスバーが非表示になり、リストアイテムが上部ベゼルの背後で端から端までスクロールされます。
  • アイドル状態: スクロールを停止して画面を離すと、約 2 秒後にステータスバーが自動的に再表示されます。

一般的なシナリオとカスタマイズを処理する

以降のセクションでは、システム ステータスバーを操作する際に発生する可能性のある一般的なシナリオの処理方法について説明します。

スクロールしない(静的な)画面を処理する

スクロール リストではなく静的レイアウト(Column や Box など)を使用する画面では、ScreenScaffold が提供する contentPadding をルート レイアウトに適用して、上部の UI 要素がシステム ステータスバーと重ならないようにします。

AppScaffold {
    ScreenScaffold { contentPadding ->
        Column(modifier = Modifier.padding(contentPadding)) {
            Text("Static Screen Content")
        }
    }
}

全画面表示のインタラクティブ コンポーネントを操作する

Wear Compose Material 3 の全画面インタラクティブ コンポーネント(AlertDialog、Dialog、Stepper、TimePicker、DatePicker など)は、画面に表示されている間、システム ステータスバーを自動的に非表示にします。これにより、コントロールは円形ディスプレイの上部に完全にアクセスできます。閉じると、ウィンドウ インセット コントローラ API を使用して、システム ステータスバーが自動的に復元されます。

没入モードに切り替える

没入モード(システム ステータスバーとローカル TimeText の両方が非表示になるモード)に切り替えるには、Modifier.suppressStatusBar 修飾子をアプリ、画面、またはコンポーネントに適用します。没入モードは、ユーザーが短時間だけ見る画面(確認オーバーレイ、ダイアログ)や、ディスプレイ全体を使用する画面(全画面の進行状況インジケーター)に限定して使用します。

画面レベルの没入モードを使用するタイミングを検討する

アプリの特定の画面のみを全画面表示または没入型にする場合は、Modifier.suppressStatusBar を ScreenScaffold に適用します。その画面がアクティブな間、ScreenScaffold は自動的にシステム ステータスバーを非表示にし、上部のステータスバーのインセット パディングを 0.dp に折りたたみます。他の画面に戻ると、ScreenScaffold はシステム ステータスバーを復元します。

AppScaffold {
    ScreenScaffold(modifier = Modifier.suppressStatusBar()) { contentPadding ->
        Box(modifier = Modifier.fillMaxSize().padding(contentPadding)) {
            // Full-screen immersive content
        }
    }
}

個々のコンポーネントが画面に表示されているときにシステム ステータスバーを非表示にするには、そのコンポーネントに Modifier.suppressStatusBar を直接適用します。この修飾子は、全画面表示の進行状況インジケーターやカスタム ダイアログ ウィンドウなど、全画面表示のコンポーネントに適用すると便利です。

アプリレベルの没入モードを使用するタイミングを検討する

アプリ全体が没入型の全画面表示エクスペリエンスである場合は、Modifier.suppressStatusBar を AppScaffold に適用して、すべての画面でシステム ステータスバーを非表示にします。

// Apply Modifier.suppressStatusBar() to AppScaffold to suppress the status bar
// and collapse insets across all child screens.
AppScaffold(modifier = Modifier.suppressStatusBar()) {
    SwipeDismissableNavHost(
        navController = navController,
        startDestination = "home",
    ) {
        // Nested screens inherit suppression and 0.dp padding
    }
}

カスタムの時刻テキストのオーバーライドを表示する

カスタムの曲線ヘッダーを表示する必要がある場合や、Wear OS 8 への移行中に特定の画面またはアプリ全体でシステム ステータスバーを一時的に無効にする必要がある場合は、カスタムの timeText コンポーザブルを渡すことで、システム ステータスバーを非表示にできます。

// Screen-level custom TimeText override:
ScreenScaffold(
    timeText = { TimeText { timeTextCurvedText("Custom Status") } },
) {
    // Content
}

// App-level opt-out using in-app TimeText:
AppScaffold(
    timeText = { TimeText() },
) {
    // Screens
}

ビューを使用して実装する(非推奨)

アプリで Wear OS 向け Jetpack Compose ではなく Android ビューを使用している場合は、AndroidManifest.xml を使用してオプトインします。Wear Compose Material 3 の最新のアルファ版では、システム ステータスバーが自動的に有効になりますが、View ベースのアプリでは、<application> 内に ENABLE_GLOBAL_STATUS_BAR <meta-data> タグを追加して明示的にオプトインする必要があります。

<meta-data
    android:name="com.google.wear.ENABLE_GLOBAL_STATUS_BAR"
    android:value="true" />

上部ウィンドウのインセットをモニタリングする

ViewCompat.setOnApplyWindowInsetsListener を使用して、windowInsets.getInsets(WindowInsetsCompat.Type.statusBars()) の値を確認します。statusBarInsets.top > 0 の場合は、以前の 12 時の UI を削除して上部のパディングを調整します。それ以外の場合は、以前の UI にフォールバックします。

ViewCompat.setOnApplyWindowInsetsListener(rootView) { view, windowInsets ->
    val statusBarInsets = windowInsets.getInsets(
        WindowInsetsCompat.Type.statusBars()
    )

    if (statusBarInsets.top > 0) {
        // status bar is visible: remove legacy 12 o'clock UI and apply top inset padding
        view.updatePadding(top = statusBarInsets.top)
    } else {
        // status bar is not visible: fall back to legacy UI and padding
    }

    WindowInsetsCompat.CONSUMED
}

プログラムでステータスバーの表示と非表示を切り替える

Compose の ScreenScaffold とは異なり、View ベースのアプリでは、WindowInsetsControllerCompat を使用して、システム ステータスバーを明示的に非表示または表示する必要があります(たとえば、全画面の没入型ビューに入る場合や、スクロールアウェイ動作を調整する場合)。

val insetsController = WindowCompat.getInsetsController(window, window.decorView)

// Hide the status bar
insetsController.hide(WindowInsetsCompat.Type.statusBars())

// Show the status bar
insetsController.show(WindowInsetsCompat.Type.statusBars())

アプリをテストする

Wear OS 7.1 ベータ版プレビュー エミュレータ(API 37.2-beta1)でアプリをテストする際は、次の点を確認してください。

  • 時計が 1 回だけ表示される: システム ステータスバーの時刻のみが 12 時の位置に表示され、以前のアプリ内 TimeText やアプリ コンテンツが重なっていないことを確認します。
  • 上部のパディング: 画面のタイトル、ヘッダー、上部のボタンがステータスバーの下に表示されることを確認します。
  • 全画面オーバーレイとダイアログ: ダイアログ、選択ツール、ステッパー、カスタムの全画面アクティビティを開いたときにステータスバーが非表示になり、閉じたときにステータスバーが復元されることを確認します。