Visualizar a interface no Compose para Wear OS

Com as visualizações do Compose no Android Studio, é possível inspecionar e verificar os elementos combináveis do Wear OS em diferentes tamanhos de tela de relógio, molduras redondas e escalas de fonte diretamente no ambiente de desenvolvimento integrado, sem implantar o app em um relógio físico ou emulador.

Como os dispositivos Wear OS têm telas circulares em que os cantos cortam o conteúdo e as sobreposições do sistema, como TimeText e ScrollIndicator, se curvam ao longo da borda da tela, é essencial configurar prévias especificamente para o Wear OS para detectar problemas de layout no início.


Configurar dependências de visualização

Para usar as anotações de visualização do Compose para Wear OS e as definições de dispositivo, adicione as dependências abaixo ao arquivo build.gradle.kts do módulo:

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

Escolher o que visualizar: telas ou componentes

A configuração de uma prévia depende de você estar visualizando uma tela cheia ou um componente de interface isolado.

Visualizar tela cheia (AppScaffold + ScreenScaffold)

Ao visualizar uma tela inteira, sempre encapsule o elemento combinável da tela em AppScaffold e ScreenScaffold usando uma anotação de visualização do dispositivo Wear. Isso renderiza a tela circular do relógio e garante que:

  • TimeText é renderizado na borda curva superior do mostrador do relógio.
  • O ScrollIndicator aparece ao longo da borda direita.
  • EdgeButton está posicionado corretamente e cortado na curva de baixo.
  • O preenchimento de conteúdo e o corte circular da tela refletem com precisão o hardware de relógio real.
@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 renderizado em WearDevices.SMALL_ROUND

Pequena redonda (192 x 192 dp)

WorkoutScreenPreview renderizado em WearDevices.LARGE_ROUND

Grande redondo (227 x 227 dp)

Visualizar componentes isolados

Ao visualizar componentes individuais, como um Card, Button ou ícone de status personalizado, omita o parâmetro device e use um @Preview padrão com um fundo escuro. Isso garante que as cores e o contraste do Wear Material 3 apareçam com precisão sem renderizar um mostrador de relógio circular completo:

@Preview(
    showBackground = true,
    backgroundColor = 0xFF000000
)
@Composable
fun HeartRateCardPreview() {
    MaterialTheme {
        HeartRateCard(bpm = 142, zone = "Aerobic")
    }
}
Visualização isolada do componente HeartRateCardPreview sem frame do relógio

Prévia isolada do componente (sem frame do dispositivo).


Anotações de várias visualizações integradas

O pacote androidx.wear.compose.ui.tooling.preview oferece anotações integradas que configuram automaticamente planos de fundo escuros (backgroundColor = 0xFF000000, showBackground = true) e dimensões circulares de dispositivos de relógio:

Nota O que ele renderiza Quando usar
@WearPreviewSmallRound 1 visualização em WearDevices.SMALL_ROUND (192 x 192 dp). Iteração rápida no tamanho de exibição circular mais restrito.
@WearPreviewLargeRound 1 visualização em WearDevices.LARGE_ROUND (227 x 227 dp). Como inspecionar a densidade do layout e o espaçamento extra em relógios maiores.
@WearPreviewDevices Duas prévias: SMALL_ROUND e LARGE_ROUND. Verificação padrão de vários dispositivos para cada elemento combinável de tela.
@WearPreviewFontScales Seis prévias em SMALL_ROUND em todas as escalas de fonte do Wear: pequena (0.94f), normal (1.0f), média (1.06f), grande (1.12f), maior (1.18f) e muito grande (1.24f). Verificando ajuste de texto, elipse e expansão da altura do botão.

É possível empilhar @WearPreviewDevices e @WearPreviewFontScales na mesma função de visualização para gerar uma matriz de teste abrangente:

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

Anotações de visualização personalizada e especificações de hardware

Quando você precisa de um controle mais refinado, como testar dimensões de hardware específicas, strings longas localizadas ou combinações de pior caso, é possível configurar @Preview diretamente ou definir suas próprias anotações de multipreview personalizadas.

Constantes WearDevices disponíveis e especificações de hardware personalizadas

O objeto androidx.wear.tooling.preview.devices.WearDevices fornece IDs de dispositivo padrão:

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

Para visualizar em telas redondas extragrandes (como relógios de 44 mm a 45 mm ou modelos Ultra em 240 x 240 dp), transmita uma string spec: personalizada ao parâmetro 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")
        }
    }
}

Criar uma anotação de visualização múltipla personalizada

Para inspecionar um cenário extremo, crie uma anotação de várias visualizações personalizadas que pareie a menor tela redonda com a maior escala de fonte e uma localidade detalhada (como alemão) ao lado de uma tela redonda grande padrão:

@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
Prévia da rodada grande padrão

1. Redonda grande padrão

Extremamente pequeno e redondo com a maior escala de fonte

2. Redonda extremamente pequena (fonte maior + alemão)


Visualizar colunas de rolagem (TransformingLazyColumn)

Por padrão, um TransformingLazyColumn é inicializado com o primeiro item (index = 0) fixado na parte de cima da tela. No entanto, no Wear OS, os itens mudam de altura e de cantos arredondados (SurfaceTransformation) à medida que se aproximam das bordas curvas de cima e de baixo da tela, e o EdgeButton só aparece quando a tela é rolada para baixo.

Para ver como sua lista aparece quando é rolada até a metade ou até a parte de baixo:

Etapa 1: elevar TransformingLazyColumnState no elemento combinável da tela

Permita que o elemento combinável da tela aceite um parâmetro TransformingLazyColumnState com rememberTransformingLazyColumnState() como valor padrão:

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

Etapa 2: transmitir initialAnchorItemIndex no seu @Preview

rememberTransformingLazyColumnState aceita dois parâmetros opcionais de rolagem inicial:

  • initialAnchorItemIndex: Int: quando definido como um índice não negativo (por exemplo, 3), a lista é inicializada com esse item centralizado na janela de visualização do relógio.
  • initialAnchorItemScrollOffset: Int: ajuste de pixel opcional aplicado em relação ao item de âncora centralizado.

É possível criar prévias lado a lado mostrando os estados Superior, Meio (rolado) e Inferior (EdgeButton visível) da mesma tela:

@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 fixado no topo da lista

Comando "top" (padrão -1)

InboxScreen rolou até o índice do meio 3

Médio (initialAnchorItemIndex = 3)

InboxScreen rolada para baixo com EdgeButton expandido

Parte de baixo (EdgeButton expandido)

Dica:também é possível clicar em Iniciar modo interativo em qualquer @Preview no Android Studio para rolar o TransformingLazyColumn ao vivo com o mouse ou trackpad e inspecionar a transformação SurfaceTransformation, as animações de entrada EdgeButton e o movimento ScrollIndicator em tempo real.

Proteção ScrollIndicator durante a captura de rolagem (LocalScrollCaptureInProgress)

Quando a captura de tela rolável do sistema (capturas de tela longas) ou ferramentas de teste de captura de tela de vários frames capturam uma rolagem TransformingLazyColumn, o Compose define LocalScrollCaptureInProgress.current como true ao capturar e unir vários blocos de viewport verticalmente.

Como o ScreenScaffold não oculta automaticamente o scrollIndicator durante a captura de rolagem, a sobreposição da barra de rolagem flutuante aparece repetida em todos os blocos combinados de uma captura de tela longa, a menos que você a proteja explicitamente com !LocalScrollCaptureInProgress.current:

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