Controllo delle versioni delle schede

Sui dispositivi Wear OS, i riquadri vengono visualizzati da due componenti chiave con controllo delle versioni indipendente. Per assicurarti che i riquadri delle app funzionino correttamente su tutti i dispositivi, è importante comprendere questa architettura sottostante.

  • Librerie correlate ai riquadri Jetpack: queste librerie (tra cui Wear Tiles e Wear ProtoLayout) sono incorporate nella tua app e tu, in qualità di sviluppatore, controlli le loro versioni. La tua app utilizza queste librerie per creare un oggetto TileBuilder.Tile (la struttura di dati che rappresenta il tuo 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 degli utenti. La versione del renderer non è controllata dallo sviluppatore dell'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 no.

Questo documento spiega come assicurarsi che l'app sia compatibile con diverse versioni della libreria Tiles e del renderer ProtoLayout e come eseguire la migrazione a versioni più recenti della libreria Jetpack.

Considera la compatibilità

Per creare un riquadro che funzioni correttamente su una vasta gamma di dispositivi, prendi in considerazione il supporto di varie 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 metodo getRendererSchemaVersion() dell'oggetto DeviceParameters passato al metodo onTileRequest(). 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 funzionamento del metodo come documentato (esempio).
    • Sebbene la chiamata a un metodo che richiede una versione del renderer superiore a quella disponibile sul dispositivo non causi 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. Questo è 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à visualizzata l'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 tuoi riquadri con diverse versioni del renderer, implementali in versioni diverse dell'emulatore Wear OS. Sui dispositivi fisici, gli aggiornamenti di ProtoLayout Renderer vengono forniti dal Play Store o dagli 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 tuo codice, quindi un altro approccio consiste nel dipendere da versioni diverse della libreria Jetpack durante il test dei riquadri.

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

Aggiorna le librerie Jetpack Tile per sfruttare i miglioramenti più recenti, incluse le modifiche all'interfaccia utente per integrare perfettamente i tuoi riquadri con il sistema.

Jetpack Tiles 1.5 e Jetpack ProtoLayout 1.3 introducono diversi miglioramenti e modifiche significativi. come le seguenti.

  • Un'API simile a Compose per descrivere la UI.
  • Componenti Material 3 Expressive, tra cui il pulsante che aderisce al bordo inferiore e il supporto per elementi visivi migliorati: 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 alla nuova API.

Consigli

  • Esegui la migrazione di tutte le tessere contemporaneamente. Evita di combinare le versioni dei riquadri all'interno della tua 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, sconsigliamo vivamente 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 dei riquadri. Data la natura altamente strutturata e basata su modelli dei riquadri, utilizza i design negli esempi esistenti come punto di partenza per i tuoi design.
  • Esegui test su una varietà di dimensioni dello schermo e del carattere. I riquadri sono spesso ricchi di informazioni, il che rende il testo (soprattutto se posizionato sui pulsanti) suscettibile a overflow e troncamento. Per ridurre al minimo questo problema, utilizza i componenti predefiniti ed evita personalizzazioni eccessive. Esegui test utilizzando la funzionalità di anteprima dei riquadri di Android Studio e su più dispositivi reali.

Processo di migrazione

Aggiorna le dipendenze

Innanzitutto, aggiorna il file build.gradle.kts. Aggiorna le versioni e modifica la dipendenza protolayout-material 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-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")
 }

TileService rimane in gran parte invariato

Le modifiche principali di questa migrazione riguardano i componenti della UI. Di conseguenza, l'implementazione di TileService, inclusi eventuali 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(), devi eseguire la migrazione a onRecentInteractionEventsAsync(). A partire dall'API 36, questi eventi verranno raggruppati in batch.

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 descrive l'interfaccia utente del riquadro.

Con ProtoLayout 1.3 (M3), la struttura e il flusso complessivi dei dati non sono cambiati, ma LayoutElement ora viene 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 laterale o informazioni supplementari come un breve testo). Questo layout è costruito dalla funzione primaryLayout().

Il layout di un riquadro che mostra mainSlot, titleSlot e bottomSlot
Figura 1.: Gli spazi 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)
                .setColor(argb(0xFFFFFFFF.toInt()))
                .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 di costruttori. Il pattern di builder tradizionale per i componenti UI Material3 viene 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 (temi, ambito dei componenti tramite materialScope) e definiscono il layout principale basato sugli slot (tramite primaryLayout). Entrambe devono essere chiamate esattamente una volta per layout.

Applicazione tema

Colore

Una delle caratteristiche più importanti di Material 3 Expressive è la "tematizzazione dinamica": i riquadri che attivano questa funzionalità (attiva per impostazione predefinita) vengono visualizzati nel tema fornito dal sistema (la disponibilità dipende dal dispositivo e dalla configurazione dell'utente).

Un altro cambiamento in M3 è l'espansione del numero di token di colore, che è aumentato da 4 a 29. I nuovi token di colore si trovano nella classe ColorScheme.

Tipografia

Simile a M2.5, M3 si basa in gran parte su costanti di dimensione del carattere predefinite. È sconsigliato specificare direttamente una dimensione del carattere. Queste costanti si trovano nella classe Typography 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 forma e al colore.

Un textButton (in mainSlot) con forma full:

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

Lo stesso pulsante di testo con forma small:

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

Componenti

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

Questo principio si applica al layout "root". Nella versione M2.5, si trattava di un PrimaryLayout o di un EdgeContentLayout. In M3, dopo aver stabilito un singolo MaterialScope di primo livello, viene chiamata la funzione primaryLayout(). Restituisce direttamente il layout principale (non sono necessari builder) e accetta LayoutElements per diversi "slot", ad esempio titleSlot, mainSlot e bottomSlot. Questi slot possono essere compilati con componenti UI concreti, ad esempio quelli restituiti da text(), button() o card(), oppure con strutture di layout, come Row o Column da LayoutElementBuilders.

I temi rappresentano un altro miglioramento chiave di M3. Per impostazione predefinita, gli elementi dell'interfaccia utente rispettano automaticamente le specifiche di stile M3 e supportano i 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()
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 boilerplate costruendo automaticamente i tipi interni appropriati. Questa modifica è ortogonale all'utilizzo dei componenti UI M3; se necessario, puoi utilizzare i modificatori in stile builder di ProtoLayout 1.2 con i componenti UI M3 e viceversa.

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)

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

Funzioni helper

Sebbene ProtoLayout 1.3 consenta di esprimere molti componenti dell'interfaccia utente utilizzando un'API ispirata a Compose, gli elementi di layout di base come righe e colonne di LayoutElementBuilders continuano a utilizzare il pattern builder. Per colmare questo divario stilistico e promuovere la coerenza con le nuove API dei componenti M3, valuta la possibilità di utilizzare le funzioni helper.

Senza helper

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

Con assistenti

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

Esegui la migrazione a Tiles 1.2 / ProtoLayout 1.0

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

Aggiorna le dipendenze

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

Trendy

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

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

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

Kotlin

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

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

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

Aggiorna spazi dei nomi

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

  1. Sostituisci tutte le importazioni di 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 delle altre importazioni di androidx.wear.tiles.* con androidx.wear.protolayout.*.

    Le importazioni per androidx.wear.tiles.EventBuilders, androidx.wear.tiles.RequestBuilders, androidx.wear.tiles.TileBuilders e androidx.wear.tiles.TileService devono rimanere invariate.

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

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