Wyświetlanie podglądu interfejsu w Compose na Wear OS

Podglądy w Android Studio Compose umożliwiają sprawdzanie i weryfikowanie komponentów Wear OS na różnych rozmiarach wyświetlaczy zegarków, okrągłych ramkach i skalach czcionek bezpośrednio w IDE – bez wdrażania aplikacji na fizycznym zegarku lub emulatorze.

Urządzenia z Wear OS mają okrągłe wyświetlacze, na których rogi przycinają treści, a nakładki systemowe, takie jak TimeText i ScrollIndicator, są zakrzywione wzdłuż krawędzi ekranu. Dlatego skonfigurowanie podglądów specjalnie dla Wear OS jest niezbędne, aby wcześnie wykrywać problemy z układem.


Konfigurowanie zależności podglądu

Aby używać adnotacji podglądu Wear OS Compose i definicji urządzeń, dodaj te zależności do pliku build.gradle.kts modułu:

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

Wybieranie elementów do wyświetlenia w podglądzie: ekrany czy komponenty

Sposób konfiguracji podglądu zależy od tego, czy wyświetlasz podgląd pełnego ekranu czy wyodrębnionego komponentu interfejsu.

Podgląd pełnoekranowy (AppScaffold + ScreenScaffold)

Podczas wyświetlania podglądu całego ekranu zawsze umieszczaj komponent ekranu w AppScaffold i ScreenScaffold, używając adnotacji podglądu urządzenia Wear. Spowoduje to wyświetlenie okrągłego ekranu zegarka i zapewni, że:

  • TimeText jest renderowany na górnej zakrzywionej krawędzi tarczy zegara.
  • ScrollIndicator pojawi się wzdłuż prawej ramki.
  • EdgeButton jest prawidłowo umieszczony i przypięty w dolnej części.
  • Wypełnienie treści i przycinanie okrągłego ekranu dokładnie odzwierciedlają rzeczywiste działanie sprzętu zegarka.
@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"
            )
        }
    }
}
Podgląd ekranu treningu na urządzeniu WearDevices.SMALL_ROUND

Mały okrągły (192 x 192 dp)

WorkoutScreenPreview renderowany na urządzeniach WearDevices.LARGE_ROUND

Duży okrągły (227x227dp)

Wyświetlanie podglądu wyodrębnionych komponentów

Podczas wyświetlania podglądu poszczególnych komponentów, takich jak niestandardowy element Card, Button lub chip stanu, pomiń parametr device i użyj standardowego elementu @Preview z ciemnym tłem. Dzięki temu kolory i kontrast Wear Material 3 będą wyświetlane prawidłowo bez renderowania pełnego okrągłego wyświetlacza zegarka:

@Preview(
    showBackground = true,
    backgroundColor = 0xFF000000
)
@Composable
fun HeartRateCardPreview() {
    MaterialTheme {
        HeartRateCard(bpm = 142, zone = "Aerobic")
    }
}
Podgląd wyodrębnionego komponentu HeartRateCardPreview bez ramki zegarka

Podgląd wyodrębnionego komponentu (bez ramki urządzenia).


Wbudowane adnotacje w wielokrotnym podglądzie

Pakiet androidx.wear.compose.ui.tooling.preview zawiera wbudowane adnotacje, które automatycznie konfigurują ciemne tła (backgroundColor = 0xFF000000, showBackground = true) i okrągłe wymiary urządzenia:

Adnotacja Co renderuje Kiedy używać
@WearPreviewSmallRound 1 podgląd na urządzeniu WearDevices.SMALL_ROUND (192x192 dp). Szybkie iteracje w przypadku najbardziej ograniczonego rozmiaru okrągłego wyświetlacza.
@WearPreviewLargeRound 1 podgląd na urządzeniu WearDevices.LARGE_ROUND (227x227 dp). Sprawdzanie gęstości układu i dodatkowych odstępów na większych zegarkach.
@WearPreviewDevices 2 podglądy: SMALL_ROUND i LARGE_ROUND. Standardowe sprawdzanie na wielu urządzeniach w przypadku każdego komponentu ekranu.
@WearPreviewFontScales 6 podglądów na SMALL_ROUND we wszystkich skalach czcionek Wear: mała (0.94f), normalna (1.0f), średnia (1.06f), duża (1.12f), większa (1.18f) i największa (1.24f). Sprawdzanie zawijania tekstu, stosowania wielokropka i zwiększania wysokości przycisku.

Możesz połączyć @WearPreviewDevices i @WearPreviewFontScales w tej samej funkcji podglądu, aby wygenerować kompleksowy zestaw testów:

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

Niestandardowe adnotacje w podglądzie i specyfikacje sprzętu

Jeśli potrzebujesz większej kontroli, np. chcesz przetestować konkretne wymiary sprzętu, długie zlokalizowane ciągi znaków lub kombinacje w najgorszym przypadku, możesz skonfigurować @Preview bezpośrednio lub zdefiniować własne niestandardowe adnotacje do podglądu wielu wersji.

Dostępne stałe WearDevices i niestandardowe specyfikacje sprzętowe

Obiekt androidx.wear.tooling.preview.devices.WearDevices zawiera standardowe identyfikatory urządzeń:

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

Aby wyświetlić podgląd na bardzo dużych okrągłych wyświetlaczach (np. zegarkach o średnicy 44–45 mm lub modelach Ultra o rozdzielczości 240 x 240 dp), przekaż niestandardowy ciąg znaków spec: do parametru 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")
        }
    }
}

Tworzenie niestandardowej adnotacji z wieloma podglądami

Aby sprawdzić ekstremalny scenariusz, utwórz niestandardową adnotację z wieloma podglądami, która łączy najmniejszy okrągły ekran z największą skalą czcionki i językiem z długimi słowami (np. niemieckim) oraz standardowym dużym okrągłym ekranem:

@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
Podgląd standardowego dużego okrągłego obrazu

1. Standardowy duży okrąg

Bardzo mała okrągła koperta z największą skalą czcionki

2. Ekstremalnie mała okrągła (największa czcionka + niemiecki)


Wyświetlanie podglądu przewijanych kolumn (TransformingLazyColumn)

Domyślnie element TransformingLazyColumn jest inicjowany z pierwszym elementem (index = 0) przypiętym u góry ekranu. Na Wear OS elementy zmieniają jednak wysokość i zaokrąglone rogi (SurfaceTransformation), gdy zbliżają się do zakrzywionych krawędzi ekranu u góry i u dołu, a EdgeButton pojawia się tylko po przewinięciu do dołu.

Aby zobaczyć podgląd listy po przewinięciu jej do połowy lub do końca:

Krok 1. Przenieś TransformingLazyColumnState do komponentu kompozycyjnego ekranu

Zezwól na akceptowanie przez komponent ekranu parametru TransformingLazyColumnState z wartością domyślną rememberTransformingLazyColumnState():

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

Krok 2. Przekaż initialAnchorItemIndex w @Preview

rememberTransformingLazyColumnState akceptuje 2 opcjonalne parametry początkowego przewijania:

  • initialAnchorItemIndex: Int: jeśli ustawisz indeks nieujemny (np. 3), lista zostanie zainicjowana z tym elementem wyśrodkowanym w obszarze wyświetlania zegarka.
  • initialAnchorItemScrollOffset: Int: opcjonalne przesunięcie piksela zastosowane względem wyśrodkowanego elementu kotwicy.

Możesz utworzyć podglądy obok siebie, które pokazują stany górny, środkowy (po przewinięciu) i dolny (EdgeButton widoczny) tego samego ekranu:

@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
                )
            )
        }
    }
}
Ekran Skrzynka odbiorcza przypięty na górze listy

Góra (domyślnie -1)

Przewinięto ekran skrzynki odbiorczej do środkowego indeksu 3

Środkowy (initialAnchorItemIndex = 3)

Ekran skrzynki odbiorczej przewinięty na dół z rozwiniętym przyciskiem EdgeButton

Dół (EdgeButton rozwinięty)

Wskazówka: możesz też kliknąć Start Interactive Mode (Włącz tryb interaktywny) w dowolnym @Preview w Android Studio, aby przewijać TransformingLazyColumn na żywo za pomocą myszy lub trackpada i sprawdzać SurfaceTransformation przekształcenia, EdgeButton animacje wejścia i ScrollIndicator ruch w czasie rzeczywistym.

Ochrona ScrollIndicator podczas robienia zrzutu ekranu z przewijaniem (LocalScrollCaptureInProgress)

Gdy systemowe narzędzia do przechwytywania zrzutów ekranu z przewijaniem (długich zrzutów ekranu) lub zrzutów wieloklatkowych przechwytują przewijany element TransformingLazyColumn, Compose ustawia wartość LocalScrollCaptureInProgress.current na true podczas przechwytywania i łączenia wielu kafelków widoku w pionie.

Ponieważ ScreenScaffold nie ukrywa automatycznie swojego elementu scrollIndicator podczas przechwytywania przewijania, nakładka pływającego paska przewijania będzie powtarzana na każdym połączonym kafelku długiego zrzutu ekranu, chyba że jawnie zabezpieczysz ją za pomocą !LocalScrollCaptureInProgress.current:

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