Creazione di una UI in sintesi

Questa pagina descrive come gestire le dimensioni e fornire layout flessibili e adattabili con la funzionalità Glance utilizzando i componenti Glance esistenti.

Utilizza Box, Column e Row

Il riepilogo ha tre layout componibili principali:

  • Box: posiziona gli elementi sopra un altro. Viene tradotto in RelativeLayout.

  • Column: posiziona gli elementi uno dopo l'altro sull'asse verticale. Traduci in LinearLayout con orientamento verticale.

  • Row: posiziona gli elementi uno dopo l'altro sull'asse orizzontale. Traduci in LinearLayout con orientamento orizzontale.

Riepilogo supporta gli oggetti Scaffold. Posiziona gli elementi componibili Column, Row e Box all'interno di un determinato oggetto Scaffold.

Immagine di un layout di colonne, righe e caselle.
Figura 1. Esempi di layout con Colonne, Riga e Riquadro.

Ciascuno di questi elementi componibili ti consente di definire gli allineamenti verticali e orizzontali dei contenuti e i vincoli di larghezza, altezza, peso o spaziatura interna tramite i modificatori. Inoltre, ogni asset secondario può definire il proprio modificatore per cambiare lo spazio e il posizionamento all'interno dell'elemento principale.

L'esempio seguente mostra come creare un elemento Row che distribuisca uniformemente gli elementi secondari orizzontalmente, come illustrato nella Figura 1:

Row(modifier = GlanceModifier.fillMaxWidth().padding(16.dp)) {
    val modifier = GlanceModifier.defaultWeight()
    Text("first", modifier)
    Text("second", modifier)
    Text("third", modifier)
}

Row occupa la larghezza massima disponibile e, poiché ogni figlio ha lo stesso peso, condivide in modo uniforme lo spazio disponibile. Puoi definire diversi pesi, dimensioni, spaziature o allineamenti per adattare i layout alle tue esigenze.

Utilizza layout scorrevoli

Un altro modo per fornire contenuti adattabili è renderli scorrevoli. Ciò è possibile con il componibile LazyColumn. Questo componibile consente di definire un insieme di elementi da visualizzare all'interno di un contenitore scorrevole nel widget dell'app.

I seguenti snippet mostrano diversi modi per definire gli elementi all'interno di LazyColumn.

Puoi specificare il numero di elementi:

// Remember to import Glance Composables
// import androidx.glance.appwidget.layout.LazyColumn

LazyColumn {
    items(10) { index: Int ->
        Text(
            text = "Item $index",
            modifier = GlanceModifier.fillMaxWidth()
        )
    }
}

Fornisci singoli elementi:

LazyColumn {
    item {
        Text("First Item")
    }
    item {
        Text("Second Item")
    }
}

Fornisci un elenco o un array di elementi:

LazyColumn {
    items(peopleNameList) { name ->
        Text(name)
    }
}

Puoi anche utilizzare una combinazione degli esempi precedenti:

LazyColumn {
    item {
        Text("Names:")
    }
    items(peopleNameList) { name ->
        Text(name)
    }

    // or in case you need the index:
    itemsIndexed(peopleNameList) { index, person ->
        Text("$person at index $index")
    }
}

Tieni presente che lo snippet precedente non specifica il itemId. La specifica itemId consente di migliorare il rendimento e mantenere la posizione di scorrimento dell'elenco e gli aggiornamenti di appWidget a partire da Android 12 (ad esempio, quando si aggiungono o rimuovono elementi dall'elenco). L'esempio seguente mostra come specificare un itemId:

items(items = peopleList, key = { person -> person.id }) { person ->
    Text(person.name)
}

Definisci il SizeMode

Le dimensioni di AppWidget possono variare a seconda del dispositivo, della scelta dell'utente o dell'Avvio app, quindi è importante fornire layout flessibili come descritto nella pagina Fornire layout flessibili dei widget. Il Riepilogo semplifica tutto ciò con la definizione SizeMode e il valore LocalSize. Le seguenti sezioni descrivono le tre modalità.

SizeMode.Single

SizeMode.Single è la modalità predefinita. Indica che viene fornito un solo tipo di contenuti, vale a dire che, anche se le dimensioni disponibili AppWidget cambiano, le dimensioni dei contenuti rimangono invariate.

class MyAppWidget : GlanceAppWidget() {

    override val sizeMode = SizeMode.Single

    override suspend fun provideGlance(context: Context, id: GlanceId) {
        // ...

        provideContent {
            MyContent()
        }
    }

    @Composable
    private fun MyContent() {
        // Size will be the minimum size or resizable
        // size defined in the App Widget metadata
        val size = LocalSize.current
        // ...
    }
}

Quando utilizzi questa modalità, assicurati che:

  • I valori dei metadati delle dimensioni minima e massima sono definiti correttamente in base alle dimensioni dei contenuti.
  • I contenuti sono sufficientemente flessibili all'interno dell'intervallo di dimensioni previsto.

In generale, dovresti utilizzare questa modalità quando:

a) l'elemento AppWidget ha una dimensione fissa oppure b) non cambia i contenuti quando viene ridimensionato.

SizeMode.Responsive

Questa modalità è equivalente alla fornitura di layout adattabili, che consente a GlanceAppWidget di definire un insieme di layout adattabili limitati da dimensioni specifiche. Per ogni dimensione definita, i contenuti vengono creati e mappati alla dimensione specifica quando l'elemento AppWidget viene creato o aggiornato. Il sistema seleziona quindi quello più adatto in base alla dimensione disponibile.

Ad esempio, nel nostro AppWidget di destinazione, puoi definire tre dimensioni e i relativi contenuti:

class MyAppWidget : GlanceAppWidget() {

    companion object {
        private val SMALL_SQUARE = DpSize(100.dp, 100.dp)
        private val HORIZONTAL_RECTANGLE = DpSize(250.dp, 100.dp)
        private val BIG_SQUARE = DpSize(250.dp, 250.dp)
    }

    override val sizeMode = SizeMode.Responsive(
        setOf(
            SMALL_SQUARE,
            HORIZONTAL_RECTANGLE,
            BIG_SQUARE
        )
    )

    override suspend fun provideGlance(context: Context, id: GlanceId) {
        // ...

        provideContent {
            MyContent()
        }
    }

    @Composable
    private fun MyContent() {
        // Size will be one of the sizes defined above.
        val size = LocalSize.current
        Column {
            if (size.height >= BIG_SQUARE.height) {
                Text(text = "Where to?", modifier = GlanceModifier.padding(12.dp))
            }
            Row(horizontalAlignment = Alignment.CenterHorizontally) {
                Button()
                Button()
                if (size.width >= HORIZONTAL_RECTANGLE.width) {
                    Button("School")
                }
            }
            if (size.height >= BIG_SQUARE.height) {
                Text(text = "provided by X")
            }
        }
    }
}

Nell'esempio precedente, il metodo provideContent viene chiamato tre volte e mappato alla dimensione definita.

  • Nella prima chiamata, la dimensione restituisce 100x100. I contenuti non includono il pulsante aggiuntivo, né i testi superiore e inferiore.
  • Nella seconda chiamata, la dimensione restituisce 250x100. I contenuti includono il pulsante aggiuntivo, ma non i testi superiore e inferiore.
  • Nella terza chiamata, la dimensione restituisce 250x250. I contenuti includono il pulsante aggiuntivo ed entrambi i testi.

SizeMode.Responsive è una combinazione delle altre due modalità e consente di definire i contenuti adattabili entro limiti predefiniti. In generale, questa modalità offre prestazioni migliori e consente transizioni più fluide quando l'elemento AppWidget viene ridimensionato.

La tabella seguente mostra il valore della dimensione, in base alla dimensione disponibile (SizeMode e AppWidget):

Dimensioni disponibili 105 x 110 203 x 112 72 x 72 203 x 150
SizeMode.Single 110 x 110 110 x 110 110 x 110 110 x 110
SizeMode.Exact 105 x 110 203 x 112 72 x 72 203 x 150
SizeMode.Responsive 80 x 100 80 x 100 80 x 100 150 x 120
* I valori esatti sono solo a scopo dimostrativo.

SizeMode.Exact

SizeMode.Exact equivale a fornire layout esatti, che richiede i contenuti GlanceAppWidget ogni volta che la dimensione AppWidget disponibile cambia (ad esempio, quando l'utente ridimensiona AppWidget nella schermata Home).

Ad esempio, nel widget di destinazione, è possibile aggiungere un pulsante in più se la larghezza disponibile è superiore a un determinato valore.

class MyAppWidget : GlanceAppWidget() {

    override val sizeMode = SizeMode.Exact

    override suspend fun provideGlance(context: Context, id: GlanceId) {
        // ...

        provideContent {
            MyContent()
        }
    }

    @Composable
    private fun MyContent() {
        // Size will be the size of the AppWidget
        val size = LocalSize.current
        Column {
            Text(text = "Where to?", modifier = GlanceModifier.padding(12.dp))
            Row(horizontalAlignment = Alignment.CenterHorizontally) {
                Button()
                Button()
                if (size.width > 250.dp) {
                    Button("School")
                }
            }
        }
    }
}

Questa modalità offre più flessibilità rispetto alle altre, ma comporta alcune avvertenze:

  • AppWidget deve essere ricreato completamente ogni volta che le dimensioni cambiano. Questo può portare a problemi di prestazioni e salti nell'interfaccia utente quando i contenuti sono complessi.
  • Le dimensioni disponibili potrebbero variare a seconda dell'implementazione di Avvio app. Ad esempio, se Avvio app non fornisce l'elenco delle dimensioni, viene utilizzata la dimensione minima possibile.
  • Nei dispositivi precedenti ad Android 12, la logica di calcolo delle dimensioni potrebbe non funzionare in tutte le situazioni.

In generale, dovresti utilizzare questa modalità se non è possibile usare SizeMode.Responsive (ovvero, non è possibile usare un piccolo insieme di layout adattabili).

Accedi alle risorse

Utilizza LocalContext.current per accedere a qualsiasi risorsa Android, come mostrato nell'esempio seguente:

LocalContext.current.getString(R.string.glance_title)

Ti consigliamo di fornire direttamente gli ID risorsa per ridurre le dimensioni dell'oggetto RemoteViews finale e per abilitare risorse dinamiche, ad esempio i colori dinamici.

Gli elementi componibili e i metodi accettano risorse utilizzando un "provider", come ImageProvider, o un metodo di sovraccarico come GlanceModifier.background(R.color.blue). Ecco alcuni esempi:

Column(
    modifier = GlanceModifier.background(R.color.default_widget_background)
) { /**...*/ }

Image(
    provider = ImageProvider(R.drawable.ic_logo),
    contentDescription = "My image",
)

Testo dell'handle

Glance 1.1.0 include un'API per impostare gli stili di testo. Imposta gli stili di testo utilizzando gli attributi fontSize, fontWeight o fontFamily della classe TextStyle.

fontFamily supporta tutti i caratteri di sistema, come mostrato nell'esempio seguente, ma i caratteri personalizzati nelle app non sono supportati:

Text(
    style = TextStyle(
        fontWeight = FontWeight.Bold,
        fontSize = 18.sp,
        fontFamily = FontFamily.Monospace
    ),
    text = "Example Text"
)

Aggiungi pulsanti composti

I pulsanti composti sono stati introdotti in Android 12. Riepilogo supporta la compatibilità con le versioni precedenti per i seguenti tipi di pulsanti composti:

Questi pulsanti composti mostrano ciascuno una visualizzazione cliccabile che rappresenta lo stato "selezionato".

var isApplesChecked by remember { mutableStateOf(false) }
var isEnabledSwitched by remember { mutableStateOf(false) }
var isRadioChecked by remember { mutableStateOf(0) }

CheckBox(
    checked = isApplesChecked,
    onCheckedChange = { isApplesChecked = !isApplesChecked },
    text = "Apples"
)

Switch(
    checked = isEnabledSwitched,
    onCheckedChange = { isEnabledSwitched = !isEnabledSwitched },
    text = "Enabled"
)

RadioButton(
    checked = isRadioChecked == 1,
    onClick = { isRadioChecked = 1 },
    text = "Checked"
)

Quando lo stato cambia, viene attivato il lambda fornito. Puoi archiviare lo stato di controllo, come mostrato nell'esempio seguente:

class MyAppWidget : GlanceAppWidget() {

    override suspend fun provideGlance(context: Context, id: GlanceId) {
        val myRepository = MyRepository.getInstance()

        provideContent {
            val scope = rememberCoroutineScope()

            val saveApple: (Boolean) -> Unit =
                { scope.launch { myRepository.saveApple(it) } }
            MyContent(saveApple)
        }
    }

    @Composable
    private fun MyContent(saveApple: (Boolean) -> Unit) {

        var isAppleChecked by remember { mutableStateOf(false) }

        Button(
            text = "Save",
            onClick = { saveApple(isAppleChecked) }
        )
    }
}

Puoi anche fornire l'attributo colors a CheckBox, Switch e RadioButton per personalizzare i relativi colori:

CheckBox(
    // ...
    colors = CheckboxDefaults.colors(
        checkedColor = ColorProvider(day = colorAccentDay, night = colorAccentNight),
        uncheckedColor = ColorProvider(day = Color.DarkGray, night = Color.LightGray)
    ),
    checked = isChecked,
    onCheckedChange = { isChecked = !isChecked }
)

Switch(
    // ...
    colors = SwitchDefaults.colors(
        checkedThumbColor = ColorProvider(day = Color.Red, night = Color.Cyan),
        uncheckedThumbColor = ColorProvider(day = Color.Green, night = Color.Magenta),
        checkedTrackColor = ColorProvider(day = Color.Blue, night = Color.Yellow),
        uncheckedTrackColor = ColorProvider(day = Color.Magenta, night = Color.Green)
    ),
    checked = isChecked,
    onCheckedChange = { isChecked = !isChecked },
    text = "Enabled"
)

RadioButton(
    // ...
    colors = RadioButtonDefaults.colors(
        checkedColor = ColorProvider(day = Color.Cyan, night = Color.Yellow),
        uncheckedColor = ColorProvider(day = Color.Red, night = Color.Blue)
    ),

)

Componenti aggiuntivi

Il Glance 1.1.0 include il rilascio di componenti aggiuntivi, come descritto nella tabella seguente:

Nome Immagine Link di riferimento Note aggiuntive
Pulsante pieno testo_alt Componente
Pulsanti con contorno testo_alt Componente
Pulsanti icona testo_alt Componente Principale / Secondario / Solo icone
Barra del titolo testo_alt Componente
Impalcatura Scaffold e Barra del titolo si trovano nella stessa demo.

Per ulteriori informazioni sulle specifiche del design, vedi i design dei componenti in questo design kit su Figma.