Creazione di una UI in sintesi

Questa pagina descrive come gestire le dimensioni e offrire flessibilità e adattabilità layout con Riepilogo, utilizzando i componenti di Riepilogo 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. Traduce a un LinearLayout con orientamento verticale.

  • Row: posiziona gli elementi uno dopo l'altro sull'asse orizzontale. Traduce a un LinearLayout con orientamento orizzontale.

Riepilogo supporta gli oggetti Scaffold. Posiziona Column, Row e Box componibili 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 componibili consente di definire gli allineamenti verticali e orizzontali del suo contenuto e i vincoli di larghezza, altezza, peso o spaziatura interna modificatori. Inoltre, ogni figlio può definire il proprio modificatore per cambiare lo spazio e posizionamento all'interno dell'elemento principale.

L'esempio seguente mostra come creare un elemento Row che distribuisce in modo uniforme i suoi figli 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 riempie la larghezza massima disponibile e poiché ogni figlio ha la stessa peso, condividono uniformemente lo spazio disponibile. Puoi definire ponderazioni diverse, dimensioni, spaziature interne o allineamenti per adattare i layout alle tue esigenze.

Utilizza layout scorrevoli

Un altro modo per fornire contenuti adattabili è renderli scorrevoli. Questo è possibile con l'elemento componibile LazyColumn. Questo componibile consente di definire 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 dell'attributo 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. Specificare i itemId aiuta a migliorare il rendimento e a mantenere lo scorrimento posizione tramite elenco e appWidget aggiornamenti a partire da Android 12 (per ad esempio quando aggiungi o rimuovi articoli dall'elenco). Nell'esempio che segue 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 di Avvio app. quindi è importante fornire layout flessibili, come descritto in Fornisci layout flessibile del widget. La funzionalità Glance semplifica questo processo con SizeMode e il valore di LocalSize. Le seguenti sezioni descrivono i tre diverse.

SizeMode.Single

SizeMode.Single è la modalità predefinita. Indica che solo un tipo di che vengano forniti i contenuti; ovvero anche se la dimensione disponibile (AppWidget) cambia, la dimensione dei contenuti non viene modificata.

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 sulla dimensione dei contenuti.
  • I contenuti sono sufficientemente flessibili all'interno dell'intervallo di dimensioni previsto.

In generale, dovresti utilizzare questa modalità quando:

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

SizeMode.Responsive

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

Ad esempio, nel nostro AppWidget di destinazione, puoi definire tre dimensioni e le relative 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 mappata alla dimensione definita.

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

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

La tabella seguente mostra il valore della dimensione, in base a SizeMode e la dimensione disponibile (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 (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 supera un certo 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 include alcune avvertenze:

  • AppWidget deve essere ricreato completamente ogni volta che le dimensioni cambiano. Questo possono causare 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, il numero minimo la dimensione massima possibile.
  • Nei dispositivi precedenti ad Android 12, la logica di calcolo delle dimensioni potrebbe non funzionare in situazioni diverse.

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

Accedi alle risorse

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

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

Ti consigliamo di fornire direttamente gli ID risorsa per ridurre le dimensioni del RemoteViews e per abilitare risorse dinamiche, ad esempio Dynamic colori.

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

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 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. La funzionalità Riepilogo supporta all'indietro compatibilità per i seguenti tipi di pulsanti composti:

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

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 verifica lo stato, come illustrato 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 fornire l'attributo colors anche a CheckBox, Switch e RadioButton per personalizzare i 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 in 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.