Wear OS 向け Compose で UI をプレビューする

Android Studio の Compose プレビューを使用すると、IDE で直接、さまざまなスマートウォッチのディスプレイ サイズ、丸いベゼル、フォント スケールにわたって Wear OS コンポーザブルを検査して検証できます。アプリを実際のスマートウォッチやエミュレータにデプロイする必要はありません。

Wear OS デバイスは、角がコンテンツを切り取り、TimeTextScrollIndicator などのシステム オーバーレイが画面の端に沿ってカーブする円形のディスプレイを備えているため、レイアウトの問題を早期に検出するには、Wear OS 専用のプレビューを構成することが不可欠です。


プレビューの依存関係を設定する

Wear OS Compose のプレビュー アノテーションとデバイス定義を使用するには、モジュールの build.gradle.kts ファイルに次の依存関係を追加します。

dependencies {
    // Provides @WearPreview* multipreview annotations
    // (such as @WearPreviewDevices and @WearPreviewFontScales)
    implementation("androidx.wear.compose:compose-ui-tooling:1.7.0")

    // Provides WearDevices constants
    // (such as WearDevices.SMALL_ROUND and WearDevices.LARGE_ROUND)
    implementation("androidx.wear:wear-tooling-preview:1.0.0")

    // Standard Compose preview support and interactive/animation inspection
    implementation("androidx.compose.ui:ui-tooling-preview")
    debugImplementation("androidx.compose.ui:ui-tooling")
}

プレビューする対象(画面またはコンポーネント)を選択する

プレビューの構成方法は、全画面をプレビューするか、分離された UI コンポーネントをプレビューするかによって異なります。

全画面表示をプレビューする(AppScaffold + ScreenScaffold

画面全体をプレビューする場合は、Wear デバイスのプレビュー アノテーションを使用して、画面コンポーザブルを常に AppScaffoldScreenScaffold の両方でラップします。これにより、円形のスマートウォッチ ディスプレイがレンダリングされ、次のことが保証されます。

  • TimeText は、文字盤の上部の曲がったエッジにレンダリングされます。
  • 右側のベゼルに ScrollIndicator が表示されます。
  • EdgeButton が適切に配置され、下部の曲線でクリップされている。
  • コンテンツのパディングと円形画面のクリッピングが、実際のスマートウォッチのハードウェアを正確に反映します。
@WearPreviewDevices
@Composable
fun WorkoutScreenPreview() {
    MaterialTheme {
        // AppScaffold provides the top-level TimeText overlay
        AppScaffold {
            // WorkoutScreen contains its own ScreenScaffold and content
            WorkoutScreen(
                heartRate = 142,
                elapsedTime = "12:45"
            )
        }
    }
}
WearDevices.SMALL_ROUND でレンダリングされた WorkoutScreenPreview

Small Round(192x192dp)

WearDevices.LARGE_ROUND でレンダリングされた WorkoutScreenPreview

大きな円形(227×227dp)

分離されたコンポーネントをプレビューする

カスタムの CardButton、ステータス チップなどの個々のコンポーネントをプレビューする場合は、device パラメータを省略し、暗い背景の標準の @Preview を使用します。これにより、円形の時計ディスプレイ全体をレンダリングしなくても、Wear Material 3 の色とコントラストが正確に表示されます。

@Preview(
    showBackground = true,
    backgroundColor = 0xFF000000
)
@Composable
fun HeartRateCardPreview() {
    MaterialTheme {
        HeartRateCard(bpm = 142, zone = "Aerobic")
    }
}
時計のフレームなしの HeartRateCardPreview 分離コンポーネントのプレビュー

分離されたコンポーネントのプレビュー(デバイス フレームなし)。


組み込みのマルチプレビュー アノテーション

androidx.wear.compose.ui.tooling.preview パッケージには、暗い背景(backgroundColor = 0xFF000000showBackground = true)と円形のスマートウォッチの寸法を自動的に構成する組み込みのアノテーションが用意されています。

Annotation レンダリングされる内容 使用する場面
@WearPreviewSmallRound WearDevices.SMALL_ROUND のプレビュー 1 つ(192×192 dp)。 最も制約の厳しい円形表示サイズでの迅速なイテレーション。
@WearPreviewLargeRound WearDevices.LARGE_ROUND(227×227 dp)のプレビュー 1 つ。 大きなスマートウォッチのレイアウト密度と余分なスペースを検査。
@WearPreviewDevices 2 つのプレビュー: SMALL_ROUNDLARGE_ROUND すべての画面コンポーザブルの標準マルチデバイス チェック。
@WearPreviewFontScales すべての Wear フォントスケール(小(0.94f)、標準(1.0f)、中(1.06f)、大(1.12f)、大(1.18f)、最大(1.24f))で SMALL_ROUND6 つのプレビュー テキストの折り返し、省略記号、ボタンの高さの拡張を確認しています。

@WearPreviewDevices@WearPreviewFontScales を同じプレビュー関数にスタックして、包括的なテスト マトリックスを生成できます。

@WearPreviewDevices
@WearPreviewFontScales
@Composable
fun MessageDetailScreenPreview() {
    MaterialTheme {
        AppScaffold {
            MessageDetailScreen(
                sender = "Alex",
                body = "Running 5 mins late!"
            )
        }
    }
}

カスタム プレビューのアノテーションとハードウェア仕様

特定のハードウェアの寸法、長いローカライズされた文字列、最悪の組み合わせなどをテストするなど、より細かい制御が必要な場合は、@Preview を直接構成するか、独自のカスタム マルチプレビュー アノテーションを定義できます。

利用可能な WearDevices 定数とカスタム ハードウェア仕様

androidx.wear.tooling.preview.devices.WearDevices オブジェクトは、標準のデバイス ID を提供します。

  • WearDevices.SMALL_ROUND"id:wearos_small_round"、192x192dp)
  • WearDevices.LARGE_ROUND"id:wearos_large_round"、227×227 dp)

特大の丸型ディスプレイ(44 ~ 45 mm のスマートウォッチや 240×240 dp の Ultra モデルなど)でプレビューするには、カスタムの spec: 文字列を device パラメータに渡します。

@Preview(
    name = "XL Round Watch (240dp)",
    device = "spec:width=240dp,height=240dp,dpi=320,isRound=true",
    showBackground = true,
    backgroundColor = 0xFF000000
)
@Composable
fun WorkoutScreenXlPreview() {
    MaterialTheme {
        AppScaffold {
            WorkoutScreen(heartRate = 142, elapsedTime = "12:45")
        }
    }
}

カスタム マルチプレビュー アノテーションを作成する

極端なシナリオを検査するには、最小の丸型スクリーン最大のフォントスケール、詳細なロケール(ドイツ語など)を標準の大型丸型スクリーンと組み合わせるカスタムのマルチプレビュー アノテーションを作成します。

@Preview(
    name = "1. Standard Large Round",
    group = "Layout extremes",
    device = WearDevices.LARGE_ROUND,
    backgroundColor = 0xFF000000,
    showBackground = true
)
@Preview(
    name = "2. Extreme Small Round (Largest Font + German)",
    group = "Layout extremes",
    device = WearDevices.SMALL_ROUND,
    fontScale = 1.24f,
    locale = "de-rDE",
    backgroundColor = 0xFF000000,
    showBackground = true
)
annotation class WearPreviewExtremes
Standard Large Round のプレビュー

1. 標準の大きな円形

Extreme Small Round with Largest font scale

2. Extreme Small Round(最大フォント + ドイツ語)


スクロールする列をプレビューする(TransformingLazyColumn

デフォルトでは、TransformingLazyColumn は最初のアイテム(index = 0)が画面の上部に固定された状態で初期化されます。ただし、Wear OS では、アイテムが画面の上端と下端の曲線に近づくにつれて、高さと角の丸み(SurfaceTransformation)が変化し、EdgeButton は下端までスクロールしたときにのみ表示されます。

リストを途中でスクロールした場合や、リストの末尾までスクロールした場合のリストの表示をプレビューするには:

ステップ 1: 画面コンポーザブルで TransformingLazyColumnState をホイストする

画面コンポーザブルが rememberTransformingLazyColumnState() をデフォルト値とする TransformingLazyColumnState パラメータを受け入れるようにします。

@Composable
fun InboxScreen(
    messages: List<Message>,
    columnState: TransformingLazyColumnState = rememberTransformingLazyColumnState(),
) {
    val transformationSpec = rememberTransformationSpec()

    ScreenScaffold(
        scrollState = columnState,
        edgeButton = {
            EdgeButton(onClick = { /* Compose new */ }) {
                Text("New message")
            }
        }
    ) { contentPadding ->
        TransformingLazyColumn(
            state = columnState,
            contentPadding = contentPadding,
        ) {
            items(messages.size) { index ->
                Card(
                    onClick = {},
                    modifier = Modifier
                        .fillMaxWidth()
                        .transformedHeight(this, transformationSpec)
                        .minimumVerticalContentPadding(
                            CardDefaults.minimumVerticalListContentPadding
                        ),
                    transformation = SurfaceTransformation(transformationSpec),
                ) {
                    Text(messages[index].subject)
                }
            }
        }
    }
}

ステップ 2: @PreviewinitialAnchorItemIndex を渡す

rememberTransformingLazyColumnState は、省略可能な初期スクロール パラメータを 2 つ受け取ります。

  • initialAnchorItemIndex: Int: 負でないインデックス(3 など)に設定すると、リストはその項目で初期化され、ウォッチのビューポートの中央に配置されます。
  • initialAnchorItemScrollOffset: Int: 中央に配置されたアンカー アイテムを基準に適用される、省略可能なピクセル オフセット。

まったく同じ画面の []、[中(スクロール)]、[下(EdgeButton が表示)] の状態を示す並列プレビューを作成できます。

@WearPreviewLargeRound
@Composable
fun InboxScreenTopPreview() {
    MaterialTheme {
        AppScaffold {
            // Default (-1): Pinned to top of list (index 0)
            InboxScreen(messages = sampleMessages)
        }
    }
}

@WearPreviewLargeRound
@Composable
fun InboxScreenScrolledMiddlePreview() {
    MaterialTheme {
        AppScaffold {
            // Centers item index 3 in the viewport, showing top/bottom item morphing
            InboxScreen(
                messages = sampleMessages,
                columnState = rememberTransformingLazyColumnState(
                    initialAnchorItemIndex = 3
                )
            )
        }
    }
}

@WearPreviewLargeRound
@Composable
fun InboxScreenBottomEdgeButtonPreview() {
    MaterialTheme {
        AppScaffold {
            // Anchors on the last item so the EdgeButton is visible at the bottom
            InboxScreen(
                messages = sampleMessages,
                columnState = rememberTransformingLazyColumnState(
                    initialAnchorItemIndex = sampleMessages.lastIndex
                )
            )
        }
    }
}
リストの先頭に固定された InboxScreen

上(デフォルト -1

InboxScreen が中央のインデックス 3 までスクロールされた

中(initialAnchorItemIndex = 3

EdgeButton が展開された状態で一番下までスクロールされた InboxScreen

下部(EdgeButton 展開)

ヒント: Android Studio の任意の @Preview で [Start Interactive Mode] をクリックして、マウスまたはトラックパッドで TransformingLazyColumn をスクロールし、SurfaceTransformation のモーフィング、EdgeButton のエントランス アニメーション、ScrollIndicator の動きをリアルタイムで検査することもできます。

スクロール キャプチャ(LocalScrollCaptureInProgress)中の ScrollIndicator をガード

システム スクロール キャプチャ(長いスクリーンショット)またはマルチフレーム スクリーンショット テストツールがスクロールする TransformingLazyColumn をキャプチャすると、Compose は、複数のビューポート タイルを縦方向にキャプチャしてステッチしている間、LocalScrollCaptureInProgress.currenttrue に設定します。

ScreenScaffold はスクロール キャプチャ中に scrollIndicator を自動的に非表示にしないため、!LocalScrollCaptureInProgress.current で明示的に保護しない限り、長いスクリーンショットのすべてのステッチ タイルにフローティング スクロールバー オーバーレイが繰り返し表示されます。

ScreenScaffold(
    scrollState = columnState,
    scrollIndicator = {
        if (!LocalScrollCaptureInProgress.current) {
            ScrollIndicator(state = columnState)
        }
    }
) { contentPadding ->
    // TransformingLazyColumn content...
    // ...
}