Controllo delle versioni delle schede

Sui dispositivi Wear OS, il rendering dei riquadri viene eseguito da due componenti chiave con versioni indipendenti. Per garantire che i riquadri della tua app funzionino correttamente su tutti i dispositivi, è importante comprendere questa architettura sottostante.

  • Librerie Jetpack correlate ai riquadri: queste librerie (tra cui Wear Tiles e Wear ProtoLayout) sono incorporate nella tua app e tu, in qualità di sviluppatore, ne controlli le versioni. La tua app utilizza queste librerie per creare un TileBuilder.Tile oggetto (la struttura di dati che rappresenta il riquadro) in risposta alla chiamata onTileRequest() del sistema.
  • Renderer ProtoLayout: questo componente di sistema è responsabile del rendering dell'oggetto Tile sul display e della gestione delle interazioni dell'utente. La versione del renderer non è controllata dallo sviluppatore di app e può variare a seconda dei dispositivi, anche di quelli con hardware identico.

L'aspetto o il comportamento di un riquadro può variare in base alle versioni della libreria Jetpack Tiles della tua app e alla versione del renderer ProtoLayout sul dispositivo dell'utente. Ad esempio, un dispositivo potrebbe supportare la rotazione o la visualizzazione dei dati sulla frequenza cardiaca, mentre un altro potrebbe non supportarli.

Questo documento spiega come rendere la tua app compatibile con le diverse versioni della libreria Tiles e del renderer ProtoLayout. Spiega anche come eseguire la migrazione a versioni più recenti della libreria Jetpack.

Considerare la compatibilità

Per creare un riquadro che funzioni correttamente su una serie di dispositivi, valuta la possibilità di tenere conto del supporto variabile delle funzionalità. Puoi farlo tramite due strategie principali: rilevare le funzionalità del renderer in fase di runtime e fornire fallback integrati.

Rilevare le funzionalità del renderer

Puoi modificare dinamicamente il layout del riquadro in base alle funzionalità disponibili su un determinato dispositivo.

Rilevare la versione del renderer

  • Utilizza il getRendererSchemaVersion() metodo dell' DeviceParameters oggetto passato al tuo onTileRequest() metodo. Questo metodo restituisce i numeri di versione principale e secondaria del renderer ProtoLayout sul dispositivo.
  • Puoi quindi utilizzare la logica condizionale nell'implementazione di onTileRequest() per adattare il design o il comportamento del riquadro in base alla versione del renderer rilevata.

L'annotazione @RequiresSchemaVersion

  • L'annotazione @RequiresSchemaVersion sui metodi ProtoLayout indica la versione minima dello schema del renderer richiesta per il comportamento documentato del metodo (esempio).
    • Anche se la chiamata di un metodo che richiede una versione del renderer più recente di quella disponibile sul dispositivo non causa l'arresto anomalo dell'app, potrebbe comportare la mancata visualizzazione dei contenuti o l'ignoranza della funzionalità.

Esempio di rilevamento della versione

val rendererVersion = requestParams.deviceConfiguration.rendererSchemaVersion

val arcElement =
    // DashedArcLine has the annotation @RequiresSchemaVersion(major = 1, minor = 500)
    // and so is supported by renderer versions 1.500 and greater
    if (
        rendererVersion.major > 1 ||
        (rendererVersion.major == 1 && rendererVersion.minor >= 500)
    ) {
        // Use DashedArcLine if the renderer supports it …
        DashedArcLine.Builder()
            .setLength(degrees(270f))
            .setThickness(8f)
            .setLinePattern(
                LayoutElementBuilders.DashedLinePattern.Builder()
                    .setGapSize(8f)
                    .setGapInterval(10f)
                    .build()
            )
            .build()
    } else {
        // … otherwise use ArcLine.
        ArcLine.Builder().setLength(degrees(270f)).setThickness(dp(8f)).build()
    }

Fornire fallback

Alcune risorse ti consentono di definire un fallback direttamente nel builder. Spesso è più semplice rispetto al controllo della versione del renderer ed è l'approccio preferito quando disponibile.

Un caso d'uso comune è fornire un'immagine statica come fallback per un'animazione Lottie. Se il dispositivo non supporta le animazioni Lottie, verrà eseguito il rendering dell'immagine statica.

val lottieImage =
    ResourceBuilders.ImageResource.Builder()
        .setAndroidLottieResourceByResId(
            ResourceBuilders.AndroidLottieResourceByResId.Builder(R.raw.lottie)
                .setStartTrigger(createOnVisibleTrigger())
                .build()
        )
        // Fallback if lottie is not supported
        .setAndroidResourceByResId(
            ResourceBuilders.AndroidImageResourceByResId.Builder()
                .setResourceId(R.drawable.lottie_fallback)
                .build()
        )
        .build()

Testare con versioni diverse del renderer

Per testare i riquadri con versioni diverse del renderer, esegui il deployment su versioni diverse dell'emulatore Wear OS. (Sui dispositivi fisici, gli aggiornamenti del renderer ProtoLayout vengono forniti tramite il Play Store o gli aggiornamenti di sistema. Non è possibile forzare l'installazione di una versione specifica del renderer.)

La funzionalità di anteprima dei riquadri di Android Studio utilizza un renderer incorporato nella libreria Jetpack ProtoLayout da cui dipende il codice, quindi un altro approccio consiste nel dipendere da versioni diverse della libreria Jetpack durante il test dei riquadri.

Eseguire la migrazione a Tiles 1.5 / ProtoLayout 1.3 (Material 3 Expressive)

Aggiorna le librerie Jetpack Tiles per sfruttare i miglioramenti più recenti, incluse le modifiche dell'UI per integrare perfettamente i riquadri con il sistema.

Jetpack Tiles 1.5 e Jetpack ProtoLayout 1.3 introducono diversi miglioramenti e modifiche importanti. Tra cui:

  • Un'API simile a Compose per descrivere l'UI.
  • Componenti Material 3 Expressive, tra cui il pulsante sul bordo inferiore e il supporto per immagini migliorate: animazioni Lottie, più tipi di sfumature e nuovi stili di linee ad arco. - Nota: alcune di queste funzionalità possono essere utilizzate anche senza eseguire la migrazione a alla nuova API.

Consigli

Segui questi consigli quando esegui la migrazione dei riquadri:

  • Esegui la migrazione di tutti i riquadri contemporaneamente. Evita di combinare le versioni dei riquadri all'interno dell'app. Anche se i componenti Material 3 si trovano in un artefatto separato (androidx.wear.protolayout:protolayout-material3), il che rende tecnicamente possibile utilizzare sia i riquadri M2.5 che M3 nella stessa app, ti consigliamo vivamente di non adottare questo approccio a meno che non sia assolutamente necessario (ad esempio, se la tua app ha un numero elevato di riquadri che non possono essere migrati tutti contemporaneamente).
  • Adotta le linee guida per l'esperienza utente di Tiles. Data la natura altamente strutturata e basata su modelli dei riquadri, utilizza i design negli esempi esistenti come punti di partenza per i tuoi design.
  • Esegui test su una varietà di dimensioni di schermo e caratteri. I riquadri sono spesso densi di informazioni, il che rende il testo (soprattutto se posizionato sui pulsanti) soggetto a overflow e ritaglio. Per ridurre al minimo questo problema, utilizza i componenti predefiniti ed evita personalizzazioni estese. Esegui i test utilizzando la funzionalità di anteprima dei riquadri di Android Studio'se su più dispositivi reali.

Processo di migrazione

Per eseguire la migrazione dei riquadri:

Aggiornare le dipendenze

Innanzitutto, aggiorna il file build.gradle.kts. Aggiorna le versioni e modifica la protolayout-material dipendenza in protolayout-material3, come mostrato di seguito:

// In build.gradle.kts

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

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

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

TileService rimane in gran parte invariato

Le modifiche principali di questa migrazione riguardano i componenti dell'UI. Di conseguenza, l'implementazione di TileService, inclusi i meccanismi di caricamento delle risorse, dovrebbe richiedere modifiche minime o nulle.

L'eccezione principale riguarda il monitoraggio dell'attività dei riquadri: se la tua app utilizza onTileEnterEvent() o onTileLeaveEvent(), ti consigliamo di eseguire la migrazione a onRecentInteractionEventsAsync(). A partire dall'API 36, questi eventi verranno raggruppati.

Adattare il codice di generazione del layout

In ProtoLayout 1.2 (M2.5), il metodo onTileRequest() restituisce un TileBuilders.Tile. Questo oggetto conteneva vari elementi, tra cui un TimelineBuilders.Timeline, che a sua volta conteneva il LayoutElement che descriveva l'UI del riquadro.

Con ProtoLayout 1.3 (M3), anche se la struttura e il flusso complessivi dei dati non sono cambiati, l'LayoutElement viene ora creato utilizzando un approccio ispirato a Compose con un layout basato su slot definiti che sono (dall'alto verso il basso) titleSlot (facoltativo; in genere per un titolo o un'intestazione principale), mainSlot (obbligatorio; per i contenuti principali) e bottomSlot (facoltativo; spesso per azioni come un pulsante sul bordo o informazioni supplementari come testo breve). Questo layout viene creato dalla primaryLayout() funzione.

Il layout di un riquadro che mostra mainSlot, titleSlot e bottomSlot
Figura 1.: Slot di un riquadro.
Confronto tra le funzioni di layout M2.5 e 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)
                .build()
        )
        .build()

M3

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

Per evidenziare le differenze principali:

  1. Eliminazione dei builder. Il precedente pattern di builder per i componenti dell'UI Material è stato sostituito da una sintassi più dichiarativa e ispirata a Compose. Anche i componenti non UI, come String/Color/Modifiers, ricevono nuovi wrapper Kotlin.
  2. Funzioni di inizializzazione e layout standardizzate. I layout M3 si basano su funzioni di inizializzazione e struttura standardizzate: materialScope() e primaryLayout(). Queste funzioni obbligatorie inizializzano l'ambiente M3 (applicazione di temi, ambito dei componenti utilizzando materialScope) e definiscono il layout principale basato su slot (utilizzando primaryLayout). Entrambe devono essere chiamate esattamente una volta per layout.

Applicazione tema

Material 3 introduce diverse modifiche all'applicazione di temi, tra cui il colore dinamico e un set ampliato di opzioni di tipografia e forma.

Colore

Una funzionalità di spicco di Material 3 Expressive è l'"applicazione di temi dinamici": i riquadri che attivano questa funzionalità (attivata per impostazione predefinita) verranno visualizzati nel tema fornito dal sistema (la disponibilità dipende dal dispositivo e dalla configurazione dell'utente).

Un'altra modifica in M3 è l'espansione del numero di token di colore, che è aumentato da 4 a 29. I nuovi token di colore sono disponibili nella ColorScheme classe.

Tipografia

Analogamente a M2.5, M3 si basa fortemente su costanti di dimensioni dei caratteri predefinite. È sconsigliato specificare direttamente le dimensioni dei caratteri. Queste costanti si trovano nella Typography classe e offrono una gamma leggermente più ampia di opzioni più espressive.

Per tutti i dettagli, consulta la documentazione sulla tipografia.

Forma

La maggior parte dei componenti M3 può variare in base alla dimensione della forma e del colore.

Un textButton (in the mainSlot) con forma full:

Riquadro con forma "completa" (angoli più arrotondati)
Figura 2.: Riquadro con forma "full"

Lo stesso textButton con forma small:

Riquadro con forma "piccola" (angoli meno arrotondati)
Figura 3.: Riquadro con forma "small"

Componenti

I componenti M3 sono più flessibili e configurabili rispetto alle loro controparti M2.5. M2.5 spesso richiedeva componenti distinti per trattamenti visivi diversi, mentre M3 utilizza spesso un componente di base generalizzato e altamente configurabile con valori predefiniti validi.

Questo principio si applica anche al layout principale. In M2.5, si trattava di un PrimaryLayout o di un EdgeContentLayout. In M3, dopo aver stabilito un singolo MaterialScope di primo livello, chiama la primaryLayout() funzione. Questa funzione restituisce direttamente il layout principale, senza bisogno di builder, e accetta LayoutElements per diversi slot, come titleSlot, mainSlot e bottomSlot. Puoi popolare questi slot con componenti dell'UI concreti, come quelli restituiti da text(), button() o card(), o con strutture di layout, come Row o Column da LayoutElementBuilders.

I temi sono un altro miglioramento chiave di M3. Per impostazione predefinita, gli elementi dell'UI rispettano automaticamente le specifiche di stile M3 e supportano l'applicazione di temi dinamici.

M2.5 M3
Elementi interattivi
Button o Chip
Testo
Text text()
Indicatori di avanzamento
CircularProgressIndicator circularProgressIndicator() o segmentedCircularProgressIndicator()
Layout
PrimaryLayout o EdgeContentLayout primaryLayout()
buttonGroup()
Google Immagini
icon(), avatarImage() o backgroundImage()

Modificatori

In M3, Modifiers, che utilizzi per decorare o aumentare un componente, sono più simili a Compose. Questa modifica può ridurre il codice boilerplate creando automaticamente i tipi interni appropriati. Questa modifica è ortogonale all'utilizzo dei componenti dell'UI M3; se necessario, puoi utilizzare i modificatori in stile builder di ProtoLayout 1.2 con i componenti dell'UI M3 e viceversa.

M2.5

// Uses Builder-style modifier to set opacity
fun myModifier(): ModifiersBuilders.Modifiers =
    ModifiersBuilders.Modifiers.Builder()
        .setOpacity(TypeBuilders.FloatProp.Builder(0.5F).build())
        .build()

M3

// Uses Compose-like modifiers to set opacity
fun myModifier(): LayoutModifier = LayoutModifier.opacity(0.5F)

Puoi creare modificatori utilizzando uno dei due stili API e puoi anche utilizzare la toProtoLayoutModifiers() funzione di estensione per convertire un LayoutModifier in un ModifiersBuilders.Modifier.

Funzioni helper

Anche se ProtoLayout 1.3 consente di esprimere molti componenti dell'UI utilizzando un' API ispirata a Compose, gli elementi di layout fondamentali come righe e colonne da LayoutElementBuilders continuano a utilizzare il pattern di builder. Per colmare questa lacuna stilistica e promuovere la coerenza con le nuove API dei componenti M3, valuta la possibilità di utilizzare le funzioni helper.

Senza helper

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

Con gli helper

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

Eseguire la migrazione a Tiles 1.2 / ProtoLayout 1.0

A partire dalla versione 1.2, la maggior parte delle API di layout di Tiles si trova nello spazio dei nomi androidx.wear.protolayout. Per utilizzare le API più recenti, completa i seguenti passaggi di migrazione nel codice.

Aggiornare le dipendenze

Nel file di build del modulo dell'app, apporta le seguenti modifiche:

Alla moda

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

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

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

Kotlin

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

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

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

Aggiornare gli spazi dei nomi

Nei file di codice basati su Kotlin e Java dell'app, apporta i seguenti aggiornamenti: In alternativa, puoi eseguire questo script di ridenominazione dello spazio dei nomi.

  1. Sostituisci tutti gli import androidx.wear.tiles.material.* con androidx.wear.protolayout.material.*. Completa questo passaggio anche per la libreria androidx.wear.tiles.material.layouts.
  2. Sostituisci la maggior parte degli altri import androidx.wear.tiles.* con androidx.wear.protolayout.*.

    Gli import per androidx.wear.tiles.EventBuilders, androidx.wear.tiles.RequestBuilders, androidx.wear.tiles.TileBuilders e androidx.wear.tiles.TileService devono rimanere invariati.

  3. Rinomina alcuni metodi ritirati delle classi TileService e TileBuilder:

    1. TileBuilders: getTimeline() in getTileTimeline() e setTimeline() in setTileTimeline()
    2. TileService: onResourcesRequest() in onTileResourcesRequest()
    3. RequestBuilders.TileRequest: getDeviceParameters() in getDeviceConfiguration(), setDeviceParameters() in setDeviceConfiguration(), getState() in getCurrentState() e setState() in setCurrentState()