Prévisualiser votre UI dans Compose pour Wear OS

Les aperçus Compose d'Android Studio vous permettent d'inspecter et de vérifier vos composables Wear OS sur différentes tailles d'écran de montre, lunettes rondes et échelles de police directement dans l'IDE, sans déployer votre application sur une montre physique ni sur un émulateur.

Étant donné que les appareils Wear OS sont dotés d'écrans circulaires où les coins rognent le contenu et que les superpositions système telles que TimeText et ScrollIndicator sont incurvées le long du bord de l'écran, il est essentiel de configurer les aperçus spécifiquement pour Wear OS afin de détecter les problèmes de mise en page dès le début.


Configurer les dépendances de l'aperçu

Pour utiliser les annotations d'aperçu et les définitions d'appareils Wear OS Compose, ajoutez les dépendances suivantes au fichier build.gradle.kts de votre module :

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")
}

Choisir ce que vous souhaitez prévisualiser : écrans ou composants

La façon dont vous configurez un aperçu dépend de ce que vous prévisualisez : un écran complet ou un composant d'UI isolé.

Prévisualiser en plein écran (AppScaffold + ScreenScaffold)

Lorsque vous prévisualisez un écran entier, enveloppez toujours votre composable d'écran dans AppScaffold et ScreenScaffold à l'aide d'une annotation de prévisualisation d'appareil Wear. Cela affiche l'écran circulaire de la montre et garantit que :

  • TimeText s'affiche sur le bord supérieur incurvé du cadran.
  • ScrollIndicator s'affiche le long de la bordure de droite.
  • EdgeButton est correctement positionné et fixé au niveau de la courbe inférieure.
  • La marge intérieure et le découpage circulaire de l'écran reflètent fidèlement le matériel de la montre.
@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"
            )
        }
    }
}
WorkoutScreenPreview affiché sur WearDevices.SMALL_ROUND

Petit rond (192 x 192 dp)

WorkoutScreenPreview rendu sur WearDevices.LARGE_ROUND

Grand rond (227 x 227 dp)

Prévisualiser des composants isolés

Lorsque vous prévisualisez des composants individuels, tels qu'un Card, un Button ou un chip d'état personnalisé, omettez le paramètre device et utilisez un @Preview standard avec un arrière-plan sombre. Cela permet de s'assurer que les couleurs et le contraste de Wear Material 3 s'affichent correctement sans afficher un cadran circulaire complet :

@Preview(
    showBackground = true,
    backgroundColor = 0xFF000000
)
@Composable
fun HeartRateCardPreview() {
    MaterialTheme {
        HeartRateCard(bpm = 142, zone = "Aerobic")
    }
}
Aperçu du composant isolé HeartRateCardPreview sans cadre de montre

Aperçu isolé du composant (sans cadre d'appareil).


Annotations d'aperçus multiples intégrées

Le package androidx.wear.compose.ui.tooling.preview fournit des annotations intégrées qui configurent automatiquement les arrière-plans sombres (backgroundColor = 0xFF000000, showBackground = true) et les dimensions des appareils connectés circulaires :

Annotation Éléments affichés Quand l'utiliser
@WearPreviewSmallRound 1 aperçu sur WearDevices.SMALL_ROUND (192 x 192 dp). Itération rapide sur la taille d'affichage circulaire la plus contrainte.
@WearPreviewLargeRound 1 aperçu sur WearDevices.LARGE_ROUND (227 x 227 dp). Inspection de la densité de la mise en page et de l'espacement supplémentaire sur les montres plus grandes.
@WearPreviewDevices Deux aperçus : SMALL_ROUND et LARGE_ROUND. Vérification multi-appareil standard pour chaque composable d'écran.
@WearPreviewFontScales 6 aperçus sur SMALL_ROUND pour toutes les échelles de police Wear : petite (0.94f), normale (1.0f), moyenne (1.06f), grande (1.12f), plus grande (1.18f) et très grande (1.24f). Vérification du retour automatique à la ligne et de l'ellipse du texte, et de l'expansion de la hauteur du bouton.

Vous pouvez empiler @WearPreviewDevices et @WearPreviewFontScales sur la même fonction d'aperçu pour générer une matrice de test complète :

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

Annotations d'aperçu personnalisées et caractéristiques matérielles

Si vous avez besoin d'un contrôle plus précis (par exemple, pour tester des dimensions matérielles spécifiques, de longues chaînes localisées ou des combinaisons dans le pire des cas), vous pouvez configurer @Preview directement ou définir vos propres annotations multiprévues personnalisées.

Constantes WearDevices disponibles et caractéristiques matérielles personnalisées

L'objet androidx.wear.tooling.preview.devices.WearDevices fournit des ID d'appareil standards :

  • WearDevices.SMALL_ROUND ("id:wearos_small_round", 192 x 192 dp)
  • WearDevices.LARGE_ROUND ("id:wearos_large_round", 227x227 dp)

Pour prévisualiser sur des écrans ronds très grands (comme les montres de 44 à 45 mm ou les modèles Ultra à 240 x 240 dp), transmettez une chaîne spec: personnalisée au paramètre 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")
        }
    }
}

Créer une annotation d'aperçus multiples personnalisée

Pour inspecter un scénario extrême, créez une annotation multi-aperçu personnalisée qui associe le plus petit écran rond à la plus grande échelle de police et à une langue détaillée (comme l'allemand) à un grand écran rond standard :

@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
Aperçu de la grande forme ronde standard

1. Grand rond standard

Extrêmement petit, rond, avec la plus grande échelle de police

2. Extrêmement petit, rond (police la plus grande + allemand)


Prévisualiser les colonnes défilantes (TransformingLazyColumn)

Par défaut, un TransformingLazyColumn s'initialise avec son premier élément (index = 0) épinglé en haut de l'écran. Toutefois, sur Wear OS, la hauteur et les angles arrondis des éléments (SurfaceTransformation) se transforment à mesure qu'ils approchent des bords incurvés en haut et en bas de l'écran, et le EdgeButton n'apparaît que lorsque l'utilisateur fait défiler l'écran jusqu'en bas.

Pour prévisualiser l'apparence de votre liste lorsque vous la faites défiler à mi-chemin ou en bas :

Étape 1 : Déplacez TransformingLazyColumnState dans votre composable d'écran

Autorisez votre composable d'écran à accepter un paramètre TransformingLazyColumnState avec rememberTransformingLazyColumnState() comme valeur par défaut :

@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)
                }
            }
        }
    }
}

Étape 2 : Passez initialAnchorItemIndex dans votre @Preview

rememberTransformingLazyColumnState accepte deux paramètres de défilement initial facultatifs :

  • initialAnchorItemIndex: Int : lorsqu'il est défini sur un index non négatif (par exemple, 3), la liste s'initialise avec cet élément centré dans la fenêtre d'affichage de la montre.
  • initialAnchorItemScrollOffset: Int : décalage en pixels facultatif appliqué par rapport à l'élément d'ancrage centré.

Vous pouvez créer des aperçus côte à côte montrant les états Haut, Milieu (avec défilement) et Bas (EdgeButton visible) de la même page :

@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 épinglé en haut de la liste

Haut (par défaut -1)

Écran de la boîte de réception défilé jusqu'à l'index du milieu 3

Moyen (initialAnchorItemIndex = 3)

Écran de la boîte de réception défilé jusqu'en bas avec le bouton Edge développé

En bas (EdgeButton développé)

Conseil : Vous pouvez également cliquer sur Démarrer le mode interactif sur n'importe quel @Preview dans Android Studio pour faire défiler le TransformingLazyColumn en direct avec votre souris ou votre pavé tactile, et inspecter la morphose SurfaceTransformation, les animations d'entrée EdgeButton et le mouvement ScrollIndicator en temps réel.

Protection ScrollIndicator lors de la capture de défilement (LocalScrollCaptureInProgress)

Lorsque les outils système de capture de défilement (captures d'écran longues) ou de test de capture d'écran multiframe capturent un TransformingLazyColumn défilant, Compose définit LocalScrollCaptureInProgress.current sur true lors de la capture et de l'assemblage de plusieurs vignettes de fenêtre d'affichage verticalement.

Étant donné que ScreenScaffold ne masque pas automatiquement son scrollIndicator lors de la capture de défilement, la barre de défilement flottante superposée apparaît de manière répétée sur chaque vignette assemblée d'une longue capture d'écran, sauf si vous la protégez explicitement avec !LocalScrollCaptureInProgress.current :

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