Obsługa wersji płytek

Na urządzeniach z Wear OS kafelki są renderowane przez 2 kluczowe komponenty z niezależnym wersjonowaniem. Aby mieć pewność, że kafelki aplikacji działają prawidłowo na wszystkich urządzeniach, musisz zrozumieć tę podstawową architekturę.

  • Biblioteki związane z kafelkami Jetpacka: te biblioteki (w tym Wear Tiles i Wear ProtoLayout) są osadzone w aplikacji, a Ty jako deweloper kontrolujesz ich wersje. Aplikacja korzysta z tych bibliotek, aby w odpowiedzi na wywołanie onTileRequest() systemu utworzyć obiekt TileBuilder.Tile (strukturę danych reprezentującą kafelek).
  • ProtoLayout Renderer: ten komponent systemu odpowiada za renderowanie obiektu Tile na wyświetlaczu i zarządzanie interakcjami użytkownika. Wersja renderera nie jest kontrolowana przez dewelopera aplikacji i może się różnić na różnych urządzeniach, nawet tych z identycznym sprzętem.

Wygląd i zachowanie kafelka mogą się różnić w zależności od wersji biblioteki Jetpack Tiles w aplikacji oraz wersji interfejsu ProtoLayout Renderer na urządzeniu użytkownika. Na przykład jedno urządzenie może obsługiwać obrót lub wyświetlanie danych o tętnie, a drugie nie.

Z tego dokumentu dowiesz się, jak zapewnić zgodność aplikacji z różnymi wersjami biblioteki Tiles i renderowania ProtoLayout oraz jak przejść na nowsze wersje biblioteki Jetpack.

Zgodność

Aby utworzyć kafelek, który działa prawidłowo na różnych urządzeniach, weź pod uwagę te kwestie.

Wykrywanie wersji renderera

  • Użyj metody getRendererSchemaVersion() obiektu DeviceParameters przekazanego do metody onTileRequest(). Ta metoda zwraca główne i mniejsze numery wersji interfejsu API ProtoLayout na urządzeniu.
  • Następnie możesz użyć logiki warunkowej w implementacji onTileRequest(), aby dostosować wygląd lub zachowanie kafelka na podstawie wykrytej wersji renderera.
    • Jeśli na przykład dana animacja nie jest obsługiwana, możesz wyświetlić obraz statyczny.

Adnotacja @RequiresSchemaVersion

  • Adnotacja @RequiresSchemaVersion przy metodzie ProtoLayout wskazuje minimalną wersję schematu renderera wymaganą, aby ta metoda działała zgodnie z opisem (przykład).
    • Mimo że wywołanie metody, która wymaga nowszej wersji renderera niż ta dostępna na urządzeniu, nie spowoduje awarii aplikacji, może spowodować, że treści nie będą wyświetlane lub funkcja zostanie zignorowana.

Przykład

override fun onTileRequest(
    requestParams: TileService.TileRequest
): ListenableFuture<Tile> {
    val rendererVersion =
        requestParams.deviceConfiguration.rendererSchemaVersion
    val tile = Tile.Builder()

    if (
        rendererVersion.major > 1 ||
            (rendererVersion.major == 1 && rendererVersion.minor >= 300)
    ) {
        // Use a feature supported in renderer version 1.300 or later
        tile.setTileTimeline(/* ... */ )
    } else {
        // Provide fallback content for older renderers
        tile.setTileTimeline(/* ... */ )
    }

    return Futures.immediateFuture(tile.build())
}

Testowanie z różnymi wersjami silnika

Aby przetestować kafelki w różnych wersjach silnika renderowania, wdrocz je w różnych wersjach emulatora Wear OS. (Na urządzeniach fizycznych aktualizacje biblioteki ProtoLayout Renderer są dostarczane przez Sklep Play lub aktualizacje systemu. Nie można wymusić instalacji określonej wersji modułu renderującego.)

Funkcja podglądu kafelków w Android Studio korzysta z renderowania zawartego w bibliotece Jetpack ProtoLayout, na której opiera się kod. Innym podejściem jest korzystanie z różnych wersji biblioteki Jetpack podczas testowania kafelków.

Migracja do wersji Tiles 1.5 lub ProtoLayout 1.3 (Material 3 Expressive)

Zaktualizuj biblioteki Jetpack Tile, aby korzystać z najnowszych ulepszeń, w tym zmian interfejsu, które umożliwiają płynną integrację kafelków z systemem.

Wersje Jetpack Tiles 1.5 i Jetpack ProtoLayout 1.3 wprowadzają kilka istotnych ulepszeń i zmian. Należą do nich:

  • Interfejs API podobny do Compose do opisywania interfejsu użytkownika.
  • Komponenty Material 3 Expressive, w tym przycisk z zaokrąglonymi krawędziami u dołu, oraz obsługa ulepszonych wizualizacji: animacji Lottie, większej liczby typów gradientów i nowych stylów łuków. – Uwaga: niektórych z tych funkcji można używać bez migracji do nowego interfejsu API.

Rekomendacje

  • Przenieś wszystkie karty jednocześnie. Unikaj mieszania wersji kafelków w aplikacji. Chociaż komponenty Material 3 znajdują się w oddzielnym pliku (androidx.wear.protolayout:protolayout-material3), co technicznie umożliwia używanie w tej samej aplikacji zarówno kafelków M2.5, jak i M3, zdecydowanie odradzamy stosowanie tego podejścia, chyba że jest to absolutnie konieczne (np. jeśli aplikacja zawiera dużą liczbę kafelków, których nie da się przenieść naraz).
  • Zastosowanie wskazówek dotyczących UX kafelków. Biorąc pod uwagę to, że kafelki są bardzo uporządkowane i stworzone na podstawie szablonów, za punkt wyjścia do własnych projektów użyj projektów z dotychczasowych przykładów.
  • Testuj na różnych ekranach i przy różnych rozmiarach czcionki. Płytki często zawierają dużo informacji, przez co tekst (zwłaszcza umieszczony na przyciskach) może być za długi lub przycięty. Aby zminimalizować ryzyko, używaj gotowych komponentów i unikaj rozbudowanych dostosowań. Przetestuj projekt, korzystając z funkcji podglądu układu w Android Studio, a także na wielu rzeczywistych urządzeniach.

Proces migracji

Aktualizowanie zależności

Najpierw zaktualizuj plik build.gradle.kts. Zaktualizuj wersje i zmień zależność protolayout-material na protolayout-material3, jak pokazano poniżej:

// In build.gradle.kts

//val tilesVersion = "1.4.1"
//val protoLayoutVersion = "1.2.1"

// Use these versions for M3.
val tilesVersion = "1.5.0-rc01"
val protoLayoutVersion = "1.3.0-rc01"

 dependencies {
     // Use to implement support for wear tiles
     implementation("androidx.wear.tiles:tiles:$tilesVersion")

     // Use to utilize standard components and layouts in your tiles
     implementation("androidx.wear.protolayout:protolayout:$protoLayoutVersion")

     // Use to utilize components and layouts with Material Design in your tiles
     // implementation("androidx.wear.protolayout:protolayout-material:$protoLayoutVersion")
     implementation("androidx.wear.protolayout:protolayout-material3:$protoLayoutVersion")

     // Use to include dynamic expressions in your tiles
     implementation("androidx.wear.protolayout:protolayout-expression:$protoLayoutVersion")

     // Use to preview wear tiles in your own app
     debugImplementation("androidx.wear.tiles:tiles-renderer:$tilesVersion")

     // Use to fetch tiles from a tile provider in your tests
     testImplementation("androidx.wear.tiles:tiles-testing:$tilesVersion")
 }

Usługa TileService pozostaje w dużej mierze niezmieniona

Główne zmiany w ramach tej migracji dotyczą komponentów interfejsu użytkownika. W związku z tym implementacja TileService, w tym wszelkie mechanizmy ładowania zasobów, powinna wymagać minimalnych modyfikacji lub nie wymagać ich wcale.

Główne wyjątki dotyczą śledzenia aktywności na kafelku: jeśli Twoja aplikacja używa funkcji onTileEnterEvent() lub onTileLeaveEvent(), musisz przejść na onRecentInteractionEventsAsync(). Od wersji API 36 te zdarzenia będą grupowane.

Dostosowywanie kodu generowania układu

W ProtoLayout 1.2 (M2.5) metoda onTileRequest() zwraca TileBuilders.Tile. Obiekt zawierał różne elementy, w tym TimelineBuilders.Timeline, który z kolei zawierał LayoutElementopis interfejsu kafelka.

W ProtoLayout 1.3 (M3) ogólna struktura danych i przepływ danych nie uległy zmianie, ale element LayoutElement jest teraz tworzony w sposób zainspirowany Compose, z układem opartym na zdefiniowanych miejscach, które są (od góry do dołu) titleSlot (zazwyczaj tytuł główny lub nagłówek), mainSlot (główna treść) i bottomSlot (często działania takie jak przycisk krawędziowy lub dodatkowe informacje, np. krótki tekst). Ten układ jest tworzony przez funkcję primaryLayout().

Układ kafelka z głównym slotem, slotem na tytuł i dolnym slotem
Rysunek 1.: Miejsca na kafelki.
Porównanie funkcji układu M2.5 i M3

M2,5

fun myLayout(
    context: Context,
    deviceConfiguration: DeviceParametersBuilders.DeviceParameters
) =
    PrimaryLayout.Builder(deviceConfiguration)
        .setResponsiveContentInsetEnabled(true)
        .setContent(
            Text.Builder(context, "Hello World!")
                .setTypography(Typography.TYPOGRAPHY_BODY1)
                .setColor(argb(0xFFFFFFFF.toInt()))
                .build()
        )
        .build()

M3

fun myLayout(
    context: Context,
    deviceConfiguration: DeviceParametersBuilders.DeviceParameters,
) =
    materialScope(context, deviceConfiguration) {
        primaryLayout(mainSlot = { text("Hello, World!".layoutString) })
    }

Oto najważniejsze różnice:

  1. Usunięcie Builders. Tradycyjny wzór konstruktora dla komponentów UI Material3 został zastąpiony bardziej deklaratywną składnią inspirowaną Compose. (Nowe wrappery Kotlina otrzymują też komponenty inne niż UI, takie jak ciągi znaków, kolory i modyfikatory).
  2. Standardowe funkcje inicjowania i układania. Układy M3 korzystają ze sformalizowanych funkcji inicjalizowania i tworzenia struktury: materialScope() i primaryLayout(). Te obowiązkowe funkcje inicjują środowisko M3 (motyw, zakres komponentu za pomocą materialScope) i określają układ główny na podstawie slotu (za pomocą primaryLayout). Obie muszą być wywoływane dokładnie raz na układ.

Motywy

Kolor

Wyróżniającą cechą Material 3 Expressive jest „dynamiczne motywy”. Płytki, które mają włączoną tę funkcję (domyślnie), będą wyświetlane w ramach motywu udostępnionego przez system (dostępność zależy od urządzenia i konfiguracji użytkownika).

Kolejną zmianą w M3 jest zwiększenie liczby tokenów kolorów z 4 do 29. Nowe tokeny kolorów znajdziesz w klasie ColorScheme.

Typografia

Podobnie jak M2.5, M3 w dużej mierze korzysta z wstępnie zdefiniowanych stałych rozmiarów czcionki. Nie zalecamy bezpośredniego określania rozmiaru czcionki. Te stałe znajdują się w klasie Typography i zapewniają nieco szerszy zakres bardziej wyrazistych opcji.

Szczegółowe informacje znajdziesz w dokumentacji dotyczącej typografii.

Kształt

Większość komponentów M3 może się różnić pod względem kształtu i koloru.

textButton (w mainSlot) o kształcie full:

Płytka o „pełnym” kształcie (z bardziej zaokrąglonymi rogami)
Rysunek 2.: Płytka w kształcie „pełnym”

Ten sam przycisk tekstowy o kształcie small:

Płytka o „małym” kształcie (z mniej zaokrąglonymi rogami)
Rysunek 3.: Płytka o kształcie „mały”

Komponenty

Komponenty M3 są znacznie bardziej elastyczne i możliwe do skonfigurowania niż ich odpowiedniki M2.5. Podczas gdy M2.5 często wymagało oddzielnych komponentów do różnych wizualnych efektów, M3 często wykorzystuje ogólny, ale bardzo konfigurowalny „podstawowy” komponent z dobrymi wartościami domyślnymi.

Ta zasada dotyczy układu „root”. W wersji M2.5 był to element PrimaryLayout lub EdgeContentLayout. W M3 po utworzeniu pojedynczej funkcji MaterialScope najwyższego poziomu wywoływana jest funkcja primaryLayout(). Zwraca ona bezpośrednio główny układ (nie trzeba używać żadnych narzędzi do tworzenia). Akceptuje ona LayoutElements w kilku „boksach”, np. titleSlot, mainSlotbottomSlot. Te sloty mogą być wypełniane konkretnymi komponentami UI, takimi jak zwracane przez text(), button() lub card(), lub struktury układu, takie jak Row lub ColumnLayoutElementBuilders.

Motywy to kolejne ważne ulepszenie M3. Domyślnie elementy interfejsu użytkownika automatycznie stosują specyfikacje stylów M3 i obsługują dynamiczne motywy.

M2,5 M3
Elementy interaktywne
Button lub Chip
Text
Text text()
Wskaźniki postępu
CircularProgressIndicator circularProgressIndicator() lub segmentedCircularProgressIndicator()
Układ
PrimaryLayout lub EdgeContentLayout primaryLayout()
buttonGroup()
Obrazy
icon(), avatarImage() lub backgroundImage()

Modyfikatory

W M3 elementy Modifiers, których używasz do dekorowania lub rozszerzania komponentu, są bardziej podobne do elementów w Compose. Ta zmiana może zmniejszyć ilość kodu stałego dzięki automatycznemu tworzeniu odpowiednich typów wewnętrznych. (Ta zmiana jest niezależna od korzystania z komponentów UI M3. W razie potrzeby możesz używać modyfikatorów w stylu kreatora z ProtoLayout 1.2 z komponentami UI M3 i na odwrót).

M2,5

// A Builder-style modifier to set the opacity of an element to 0.5
fun myModifier(): ModifiersBuilders.Modifiers =
    ModifiersBuilders.Modifiers.Builder()
        .setOpacity(TypeBuilders.FloatProp.Builder(0.5F).build())
        .build()

M3

// The equivalent Compose-like modifier is much simpler
fun myModifier(): LayoutModifier = LayoutModifier.opacity(0.5F)

Możesz tworzyć modyfikatory, używając dowolnego stylu interfejsu API. Możesz też użyć funkcji rozszerzenia toProtoLayoutModifiers(), aby przekształcić LayoutModifierModifiersBuilders.Modifier.

Funkcje pomocnicze

Chociaż interfejs ProtoLayout 1.3 umożliwia definiowanie wielu komponentów UI za pomocą interfejsu API inspirowanego Compose, podstawowe elementy układu, takie jak wierszekolumnyLayoutElementBuilders, nadal korzystają z schematu konstruktora. Aby zniwelować tę różnicę stylistyczną i zapewnić spójność z nowymi interfejsami API komponentów M3, rozważ użycie funkcji pomocniczych.

Bez pomocy

primaryLayout(
    mainSlot = {
        LayoutElementBuilders.Column.Builder()
            .setWidth(expand())
            .setHeight(expand())
            .addContent(text("A".layoutString))
            .addContent(text("B".layoutString))
            .addContent(text("C".layoutString))
            .build()
    }
)

Z pomocnikami

// Function literal with receiver helper function
fun column(builder: Column.Builder.() -> Unit) =
    Column.Builder().apply(builder).build()

primaryLayout(
    mainSlot = {
        column {
            setWidth(expand())
            setHeight(expand())
            addContent(text("A".layoutString))
            addContent(text("B".layoutString))
            addContent(text("C".layoutString))
        }
    }
)

Migracja do wersji 1.2 usługi Tiles lub ProtoLayout 1.0

Od wersji 1.2 większość interfejsów API układu kafelków znajduje się w przestrzeni nazw androidx.wear.protolayout. Aby korzystać z najnowszych interfejsów API, wykonaj w kodu te czynności związane z migracją.

Aktualizowanie zależności

W pliku kompilacji modułu aplikacji wprowadź te zmiany:

Groovy

  // Remove
  implementation 'androidx.wear.tiles:tiles-material:version'

  // Include additional dependencies
  implementation "androidx.wear.protolayout:protolayout:1.2.1"
  implementation "androidx.wear.protolayout:protolayout-material:1.2.1"
  implementation "androidx.wear.protolayout:protolayout-expression:1.2.1"

  // Update
  implementation "androidx.wear.tiles:tiles:1.4.1"

Kotlin

  // Remove
  implementation("androidx.wear.tiles:tiles-material:version")

  // Include additional dependencies
  implementation("androidx.wear.protolayout:protolayout:1.2.1")
  implementation("androidx.wear.protolayout:protolayout-material:1.2.1")
  implementation("androidx.wear.protolayout:protolayout-expression:1.2.1")

  // Update
  implementation("androidx.wear.tiles:tiles:1.4.1")

Aktualizowanie przestrzeni nazw

W plikach kodu Kotlina i Javę w aplikacji wprowadź te zmiany. Możesz też uruchomić ten skrypt do zmiany nazwy przestrzeni nazw.

  1. Zastąp wszystkie importowane dane androidx.wear.tiles.material.* elementem androidx.wear.protolayout.material.*. Wykonaj ten krok również w przypadku biblioteki androidx.wear.tiles.material.layouts.
  2. Zastąp większość innych importowanych ontologii androidx.wear.tiles.* ontologią androidx.wear.protolayout.*.

    Importy dotyczące zasad androidx.wear.tiles.EventBuilders, androidx.wear.tiles.RequestBuilders, androidx.wear.tiles.TileBuilders i androidx.wear.tiles.TileService powinny pozostać bez zmian.

  3. Zmieniono nazwy kilku przestarzałych metod z klas TileService i TileBuilder:

    1. TileBuilders: getTimeline() to getTileTimeline(), a setTimeline() to setTileTimeline()
    2. TileService: z onResourcesRequest() na onTileResourcesRequest()
    3. RequestBuilders.TileRequest: getDeviceParameters() zmieni się na getDeviceConfiguration(), setDeviceParameters() na setDeviceConfiguration(), getState() na getCurrentState(), a setState() na setCurrentState()