Questa pagina descrive come gestire le dimensioni e fornire layout flessibili e adattabili con Glance.
Utilizza Box
, Column
e Row
Glance ha tre layout componibili principali:
Box
: posiziona gli elementi sopra un altro. Si traduce inRelativeLayout
.Column
: posiziona gli elementi uno dopo l'altro sull'asse verticale. Si traduce in un elementoLinearLayout
con orientamento verticale.Row
: posiziona gli elementi uno dopo l'altro sull'asse orizzontale. Si traduce in un fileLinearLayout
con orientamento orizzontale.
Ognuno di questi elementi componibili consente di definire l'allineamento verticale e orizzontale dei contenuti e i vincoli relativi a larghezza, altezza, peso o spaziatura interna utilizzando i modificatori. Inoltre, ogni figlio 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 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 asset secondario ha la stessa
peso, condivide in modo uniforme lo spazio disponibile. Puoi definire ponderazioni,
dimensioni, spaziatura interna o allineamenti diversi per adattare i layout alle tue esigenze.
Utilizzare layout scorrevoli
Un altro modo per fornire contenuti adattabili consiste nel renderli scorrevoli. Questo è 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 i 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 valore itemId
. Se specifichi itemId
, puoi migliorare le prestazioni e mantenere la posizione di scorrimento tramite l'elenco e gli aggiornamenti appWidget
a partire da Android 12 in poi (ad esempio, durante l'aggiunta o la rimozione di elementi dall'elenco). L'esempio seguente mostra come specificare un itemId
:
items(items = peopleList, key = { person -> person.id }) { person -> Text(person.name) }
Definisci SizeMode
Le dimensioni di AppWidget
possono variare a seconda del dispositivo, della scelta dell'utente o dell'Avvio app, perciò è importante fornire layout flessibili come descritto nella pagina Fornire layout flessibili dei widget. Glance semplifica questa operazione con la definizione SizeMode
e il valore LocalSize
. Le tre 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 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 minimi e massimi 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) AppWidget
ha una dimensione fissa oppure
b) non modifica i suoi contenuti quando viene ridimensionato.
SizeMode.Responsive
Questa modalità equivale a fornire layout adattabili, che consente all'GlanceAppWidget
di definire un insieme di layout adattabili limitati da dimensioni specifiche. Per ogni dimensione definita, i contenuti vengono creati e mappati alle dimensioni specifiche quando AppWidget
viene creato o aggiornato. Il sistema seleziona quindi quella
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 restituisce
100x100
. I contenuti non includono il pulsante aggiuntivo, né il testo superiore e inferiore. - Nella seconda chiamata, la dimensione restituisce
250x100
. I contenuti includono il pulsante aggiuntivo, ma non il testo 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 contenuti adattabili entro limiti predefiniti. In generale, questa modalità ha un rendimento migliore e consente transizioni più fluide quando la AppWidget
viene ridimensionata.
La tabella seguente mostra il valore della taglia, a seconda di SizeMode
e
di AppWidget
dimensione disponibile:
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 cambia la dimensione AppWidget
disponibile (ad esempio, quando l'utente ridimensiona AppWidget
nella schermata Home).
Ad esempio, nel widget di destinazione, è possibile aggiungere un pulsante extra 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 prevede alcune avvertenze:
AppWidget
deve essere ricreato completamente ogni volta che la dimensione cambia. Ciò può causare problemi di prestazioni e interruzioni dell'interfaccia utente quando i contenuti sono complessi.- Le dimensioni disponibili potrebbero variare a seconda dell'implementazione dell'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, conviene utilizzare 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 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.
I componibili e i metodi accettano le risorse utilizzando un "provider", come
ImageProvider
, o un metodo di overload 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", )
Aggiungi 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:
Ciascuno di questi pulsanti composti mostra 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 attivata la funzione lambda fornita. Puoi archiviare 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) ), )