Creazione di una UI in sintesi

Questa pagina descrive come gestire le dimensioni e fornire layout flessibili e adattabili con Riepilogo utilizzando i componenti esistenti di Riepilogo.

Utilizza Box, Column e Row

Glance ha tre layout composibili principali:

  • Box: posiziona gli elementi uno sopra l'altro. Si traduce in un RelativeLayout.

  • Column: inserisce gli elementi uno dopo l'altro nell'asse verticale. Viene visualizzato in un LinearLayout con orientamento verticale.

  • Row: inserisce gli elementi uno dopo l'altro nell'asse orizzontale. Si traduce in un LinearLayout con orientamento orizzontale.

Riepilogo supporta gli oggetti Scaffold. Inserisci i composabili 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 Colonna, Riga e Riquadro.

Ciascuno di questi composabili ti consente di definire gli allineamenti verticali e orizzontali del relativo contenuto e i vincoli di larghezza, altezza, spessore o spaziatura utilizzando i modificatori. Inoltre, ogni elemento secondario può definire il proprio modificatore per modificare lo spazio e il posizionamento all'interno dell'elemento principale.

L'esempio seguente mostra come creare un elemento Row che distribuisca uniformemente i suoi elementi secondari in orizzontale, come mostrato 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 bambino ha lo stesso peso, condividono equamente lo spazio disponibile. Puoi definire diversi spessori, dimensioni, spaziature o allineamenti per adattare i layout alle tue esigenze.

Utilizzare layout scorrevoli

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

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

Puoi fornire il numero di articoli:

// 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 articoli:

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 itemId. La specifica di itemId contribuisce a migliorare le prestazioni e a mantenere la posizione di scorrimento tramite gli aggiornamenti dell'elenco e di appWidget a partire da Android 12 (ad esempio, quando aggiungi o rimuovi 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 dei AppWidget possono variare in base al dispositivo, alla scelta dell'utente o al programma di avvio, pertanto è importante fornire layout flessibili come descritto nella pagina Fornire layout dei widget flessibili. Glance semplifica questa operazione con la definizione SizeMode e il valore LocalSize. Le sezioni seguenti descrivono le tre modalità.

SizeMode.Single

SizeMode.Single è la modalità predefinita. Indica che viene fornito un solo tipo di contenuti, ovvero che anche se le dimensioni disponibili di AppWidget cambiano, le dimensioni dei contenuti non cambiano.

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 relativi alle dimensioni minime e massime sono definiti correttamente in base alle dimensioni dei contenuti.
  • I contenuti sono sufficientemente flessibili nell'intervallo di dimensioni previsto.

In generale, ti consigliamo di utilizzare questa modalità quando:

a) l'elemento AppWidget ha dimensioni fisse oppure b) i contenuti non cambiano quando le dimensioni vengono modificate.

SizeMode.Responsive

Questa modalità è equivalente alla fornitura di layout adattabili, che consente al GlanceAppWidget di definire un insieme di layout adattabili delimitati da dimensioni specifiche. Per ogni dimensione definita, i contenuti vengono creati e mappati alla dimensione specifica quando AppWidget viene creato o aggiornato. Il sistema seleziona poi la dimensione più adatta in base alle dimensioni disponibili.

Ad esempio, nella nostra destinazione AppWidget, 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 ha il valore 100x100. I contenuti non includono il pulsante aggiuntivo né i testi in alto e in basso.
  • Nella seconda chiamata, la dimensione ha il valore 250x100. I contenuti includono il pulsante extra, ma non i testi in alto e in basso.
  • Nella terza chiamata, la dimensione ha il valore 250x250. I contenuti includono il pulsante extra e entrambi i testi.

SizeMode.Responsive è una combinazione delle altre due modalità e ti consente di definire contenuti adattabili entro limiti predefiniti. In generale, questa modalità offre un rendimento migliore e consente transizioni più fluide quando le dimensioni di AppWidget vengono modificate.

La tabella seguente mostra il valore della dimensione, a seconda delle dimensioni SizeMode e AppWidget disponibili:

Taglia disponibile 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 è l'equivalente di fornire layout esatti, che richiede i contenuti GlanceAppWidget ogni volta che le dimensioni del AppWidget disponibili cambiano (ad esempio, quando l'utente ridimensiona il AppWidget nella schermata Home).

Ad esempio, nel widget di destinazione è possibile aggiungere un pulsante aggiuntivo 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 maggiore flessibilità rispetto alle altre, ma presenta alcuni inconvenienti:

  • AppWidget deve essere ricreato completamente ogni volta che le dimensioni cambiano. Ciò può portare a problemi di prestazioni e a salti nell'interfaccia utente quando i contenuti sono complessi.
  • Le dimensioni disponibili potrebbero variare a seconda dell'implementazione del programma di avvio. Ad esempio, se il programma di avvio 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, ti consigliamo di utilizzare questa modalità se non puoi utilizzare SizeMode.Responsive (ovvero se non è possibile creare un piccolo insieme di layout adattabili).

Accedere 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 attivare le risorse dinamiche, come i colori dinamici.

I composabili e i metodi accettano le risorse utilizzando un "provider", ad esempio ImageProvider, o 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",
)

Gestire il testo

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 non supporta i caratteri personalizzati nelle app:

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

Aggiungere pulsanti composti

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

Questi pulsanti composti mostrano ciascuno una visualizzazione selezionabile 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 attivata la funzione lambda fornita. Puoi memorizzare lo stato del 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 personalizzarne 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

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

Nome Immagine Link di riferimento Note aggiuntive
Pulsante con riempimento alt_text Componente
Pulsanti con contorni alt_text Componente
Pulsanti con icone alt_text Componente Principale / secondaria / solo icona
Barra del titolo alt_text Componente
Scaffold La struttura di base e la barra del titolo si trovano nella stessa demo.

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

Per ulteriori informazioni sui layout canonici, consulta Layout dei widget canonici.