Visualizzare l'anteprima della UI in Compose per Wear OS

Le anteprime di Android Studio Compose ti consentono di ispezionare e verificare i composable di Wear OS su diverse dimensioni del display dell'orologio, cornici rotonde e scale dei caratteri direttamente nell'IDE, senza eseguire il deployment dell'app su un orologio fisico o un emulatore.

Poiché i dispositivi Wear OS sono dotati di display circolari in cui gli angoli tagliano i contenuti e le sovrapposizioni di sistema come TimeText e ScrollIndicator si curvano lungo il bordo dello schermo, la configurazione delle anteprime appositamente per Wear OS è essenziale per rilevare i problemi di layout in anticipo.


Configurare le dipendenze dell'anteprima

Per utilizzare le annotazioni di anteprima e le definizioni dei dispositivi di Wear OS Compose, aggiungi le seguenti dipendenze al file build.gradle.kts del modulo:

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

Scegli cosa visualizzare in anteprima: schermate o componenti

La configurazione di un'anteprima dipende dal tipo di anteprima che vuoi visualizzare: a schermo intero o di un componente dell'interfaccia utente isolato.

Visualizzare l'anteprima a schermo intero (AppScaffold + ScreenScaffold)

Quando visualizzi l'anteprima di un'intera schermata, racchiudi sempre il composable dello schermo sia in AppScaffold che in ScreenScaffold utilizzando un'annotazione di anteprima del dispositivo Wear. In questo modo viene visualizzato il quadrante circolare e viene garantito che:

  • TimeText viene visualizzato sul bordo curvo superiore del quadrante orologio.
  • ScrollIndicator viene visualizzato lungo la cornice destra.
  • EdgeButton è posizionato correttamente e agganciato alla curva inferiore.
  • Il riempimento dei contenuti e il ritaglio dello schermo circolare riflettono accuratamente l'hardware reale dello smartwatch.
@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 visualizzato su WearDevices.SMALL_ROUND

Small Round (192x192dp)

WorkoutScreenPreview visualizzato su WearDevices.LARGE_ROUND

Large Round (227x227dp)

Visualizzare l'anteprima dei componenti isolati

Quando visualizzi l'anteprima dei singoli componenti, ad esempio un Card, un Button o un chip di stato personalizzato, ometti il parametro device e utilizza un @Preview standard con uno sfondo scuro. In questo modo, i colori e il contrasto di Wear Material 3 vengono visualizzati in modo accurato senza eseguire il rendering di un display circolare completo dell'orologio:

@Preview(
    showBackground = true,
    backgroundColor = 0xFF000000
)
@Composable
fun HeartRateCardPreview() {
    MaterialTheme {
        HeartRateCard(bpm = 142, zone = "Aerobic")
    }
}
Anteprima del componente isolato HeartRateCardPreview senza cornice dell'orologio

Anteprima del componente isolato (senza cornice del dispositivo).


Annotazioni integrate per l'anteprima multipla

Il pacchetto androidx.wear.compose.ui.tooling.preview fornisce annotazioni integrate che configurano automaticamente sfondi scuri (backgroundColor = 0xFF000000, showBackground = true) e dimensioni circolari del dispositivo orologio:

Annotazione Cosa viene visualizzato Quando usare la funzionalità
@WearPreviewSmallRound 1 anteprima su WearDevices.SMALL_ROUND (192x192 dp). Iterazione rapida sulle dimensioni di visualizzazione circolari più vincolate.
@WearPreviewLargeRound 1 anteprima su WearDevices.LARGE_ROUND (227 x 227 dp). Ispezione della densità del layout e della spaziatura aggiuntiva sugli smartwatch più grandi.
@WearPreviewDevices 2 anteprime: SMALL_ROUND e LARGE_ROUND. Controllo standard su più dispositivi per ogni componente componibile dello schermo.
@WearPreviewFontScales 6 anteprime su SMALL_ROUND in tutte le scale dei caratteri di Wear: Piccola (0.94f), Normale (1.0f), Media (1.06f), Grande (1.12f), Più grande (1.18f) e Massima (1.24f). Controllo del ritorno a capo del testo, dell'inserimento dei puntini di sospensione e dell'espansione dell'altezza del pulsante.

Puoi combinare @WearPreviewDevices e @WearPreviewFontScales nella stessa funzione di anteprima per generare una matrice di test completa:

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

Annotazioni di anteprima personalizzate e specifiche hardware

Quando hai bisogno di un controllo più preciso, ad esempio per testare dimensioni hardware specifiche, stringhe localizzate lunghe o combinazioni nel caso peggiore, puoi configurare @Preview direttamente o definire le tue annotazioni multipreview personalizzate.

Costanti WearDevices disponibili e specifiche hardware personalizzate

L'oggetto androidx.wear.tooling.preview.devices.WearDevices fornisce ID dispositivo standard:

  • WearDevices.SMALL_ROUND ("id:wearos_small_round", 192x192dp)
  • WearDevices.LARGE_ROUND ("id:wearos_large_round", 227x227dp)

Per visualizzare l'anteprima su display rotondi extra large (come orologi da 44-45 mm o modelli Ultra a 240 x 240 dp), trasmetti una stringa spec: personalizzata al parametro 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")
        }
    }
}

Creare un'annotazione personalizzata per l'anteprima multipla

Per esaminare uno scenario estremo, crea un'annotazione personalizzata per l'anteprima multipla che accoppi lo schermo rotondo più piccolo con la scala del carattere più grande e una località dettagliata (come il tedesco) insieme a un grande schermo rotondo 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
Anteprima di Standard Large Round

1. Standard Large Round

Estremamente piccolo rotondo con la massima scala dei caratteri

2. Estremamente piccolo rotondo (carattere più grande + tedesco)


Visualizzare l'anteprima delle colonne scorrevoli (TransformingLazyColumn)

Per impostazione predefinita, un TransformingLazyColumn viene inizializzato con il primo elemento (index = 0) bloccato nella parte superiore dello schermo. Tuttavia, su Wear OS, l'altezza e gli angoli arrotondati (SurfaceTransformation) degli elementi cambiano man mano che si avvicinano ai bordi curvi superiore e inferiore dello schermo e il EdgeButton viene visualizzato solo quando si scorre fino in fondo.

Per visualizzare l'anteprima dell'aspetto dell'elenco quando viene scorre parzialmente verso il basso o in fondo all'elenco:

Passaggio 1: inserisci TransformingLazyColumnState nel componente dello schermo

Consenti al composable dello schermo di accettare un parametro TransformingLazyColumnState con rememberTransformingLazyColumnState() come valore predefinito:

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

Passaggio 2: invia initialAnchorItemIndex in @Preview

rememberTransformingLazyColumnState accetta due parametri di scorrimento iniziale facoltativi:

  • initialAnchorItemIndex: Int: se impostato su un indice non negativo (ad esempio 3), l'elenco viene inizializzato con l'elemento centrato nella viewport di visualizzazione.
  • initialAnchorItemScrollOffset: Int: offset dei pixel facoltativo applicato rispetto all'elemento di ancoraggio centrato.

Puoi creare anteprime affiancate che mostrano gli stati In alto, Al centro (con scorrimento) e In basso (EdgeButton visibile) della stessa schermata:

@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 bloccato in cima all'elenco

In alto (predefinito -1)

InboxScreen scrolled to middle index 3

Medio (initialAnchorItemIndex = 3)

Schermata Posta in arrivo scorre fino in fondo con il pulsante EdgeButton espanso

In basso (EdgeButton espanso)

Suggerimento: puoi anche fare clic su Avvia modalità interattiva su qualsiasi @Preview in Android Studio per scorrere TransformingLazyColumn in tempo reale con il mouse o il trackpad e ispezionare la trasformazione di SurfaceTransformation, le animazioni di ingresso di EdgeButton e il movimento di ScrollIndicator in tempo reale.

Guardia ScrollIndicator durante l'acquisizione con scorrimento (LocalScrollCaptureInProgress)

Quando gli strumenti di test di acquisizione con scorrimento (screenshot lunghi) o screenshot multi-frame del sistema acquisiscono uno scorrimento TransformingLazyColumn, Compose imposta LocalScrollCaptureInProgress.current su true durante l'acquisizione e l'unione verticale di più riquadri del viewport.

Poiché ScreenScaffold non nasconde automaticamente la sua scrollIndicator durante l'acquisizione dello scorrimento, la sovrapposizione della barra di scorrimento mobile verrà ripetuta su ogni riquadro unito di uno screenshot lungo, a meno che tu non la protegga esplicitamente con !LocalScrollCaptureInProgress.current:

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