Questa pagina descrive come gestire le dimensioni e fornire layout flessibili e reattivi con Glance, utilizzando i componenti Glance esistenti.
Utilizza Box, Column e Row
Glance ha tre layout componibili principali:
Box: Posiziona gli elementi uno sopra l'altro. Si traduce in unRelativeLayout.Column: posiziona gli elementi uno dopo l'altro sull'asse verticale. Si traduce in unLinearLayoutcon orientamento verticale.Row: posiziona gli elementi uno dopo l'altro nell'asse orizzontale. Si traduce in unLinearLayoutcon orientamento orizzontale.
Glance supporta gli oggetti Scaffold. Posiziona i composable Column, Row e
Box all'interno di un determinato oggetto Scaffold.
Ciascuno di questi composable ti consente di definire gli allineamenti verticale e orizzontale dei contenuti e i vincoli di 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 del genitore.
L'esempio seguente mostra come creare un Row che distribuisce uniformemente
i relativi 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 occupa la larghezza massima disponibile e, poiché ogni figlio ha lo stesso
peso, lo spazio disponibile viene condiviso in modo uniforme. Puoi definire pesi,
dimensioni, spaziatura interna o allineamenti diversi per adattare i layout alle tue esigenze.
Utilizzare layout scorrevoli
Un altro modo per fornire contenuti reattivi è renderli scorrevoli. È possibile
con il componente componibile LazyColumn. Questo componente combinabile ti consente di definire un insieme
di elementi da visualizzare all'interno di un contenitore a scorrimento nel widget dell'app.
I seguenti snippet 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 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 itemId. La specifica di
itemId contribuisce a migliorare le prestazioni e a mantenere la posizione di scorrimento
tramite gli aggiornamenti di elenchi e 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, itemId = { person -> person.id.hashCode().toLong() }) { person -> Text(person.name) }
Scorrimento rapido
Lo scorrimento rapido è un'animazione che consente ai contenuti scorrevoli di posizionarsi nella parte superiore del contenitore del widget.
Per implementare lo scorrimento rapido, assicurati di soddisfare le seguenti condizioni:
- Aggiorna la dipendenza di Glance alla versione 1.3.0-alpha02 o successive.
- Imposta il
compileSdksu 37 o un valore superiore, in quanto lo scorrimento rapido è supportato sui dispositivi con Android 17 e versioni successive. - Configura
LazyColumnconVerticalScrollMode. Se il dispositivo supporta lo scorrimento rapido, utilizzaSnapScrollMatchHeight. In caso contrario, utilizzaNormal.
Se utilizzi lo scorrimento rapido con le immagini, consulta il layout canonico immagine a tutta pagina.
@Composable fun SnapScrollLayout() { val height = LocalSize.current.height val items = listOf( ColorItem(Color.Red, "Red"), ColorItem(Color.Yellow, "Yellow"), ColorItem(Color.Blue, "Blue") ) val scrollMode = if (Build.VERSION.SDK_INT >= 37) { VerticalScrollMode.SnapScrollMatchHeight(height) } else { VerticalScrollMode.Normal } LazyColumn( verticalScrollMode = scrollMode ) { items(items) { item -> ColorCard(item, height) } } } @Composable private fun ColorCard(item: ColorItem, height: Dp) { Box( modifier = GlanceModifier .background(item.color) .fillMaxWidth() .height(height), contentAlignment = Alignment.Center ) { Text( text = item.name, modifier = GlanceModifier.background(Color.White) ) } }
Definisci il SizeMode
Le dimensioni di AppWidget possono variare a seconda del dispositivo, della scelta dell'utente o del launcher, pertanto è 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. Nelle sezioni seguenti vengono descritte le tre
modalità.
SizeMode.Single
SizeMode.Single è la modalità predefinita. Indica che viene fornito un solo tipo di
contenuto, ovvero anche se le dimensioni disponibili di AppWidget cambiano,
le dimensioni del contenuto non vengono modificate.
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 all'interno dell'intervallo di dimensioni previsto.
In generale, devi utilizzare questa modalità quando:
a) AppWidget ha una dimensione fissa oppure
b) non modifica i propri contenuti quando viene ridimensionato.
SizeMode.Responsive
Questa modalità equivale a fornire layout reattivi, il che consente a GlanceAppWidget di definire un insieme di layout reattivi delimitati da dimensioni specifiche. Per ogni dimensione definita, i contenuti vengono creati e mappati alla dimensione specifica
quando viene creato o aggiornato il AppWidget. Il sistema seleziona quindi
quella più adatta in base alla taglia disponibile.
Ad esempio, nella nostra destinazione AppWidget, puoi definire tre dimensioni e il relativo
contenuto:
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 in alto e in basso. - Nella seconda chiamata, la dimensione viene valutata come
250x100. I contenuti includono il pulsante aggiuntivo, ma non i testi in alto e in basso. - Nella terza chiamata, la dimensione viene valutata come
250x250. I contenuti includono il pulsante aggiuntivo 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 prestazioni migliori e transizioni più fluide quando viene ridimensionato il AppWidget.
La tabella seguente mostra il valore della dimensione, a seconda di SizeMode e
della 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 le dimensioni AppWidget disponibili
cambiano (ad esempio, quando l'utente ridimensiona AppWidget nella schermata Home).
Ad esempio, nel widget di destinazione è possibile aggiungere un pulsante aggiuntivo se la larghezza disponibile è maggiore di 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 avvertimenti:
- Il
AppWidgetdeve essere ricreato completamente ogni volta che le dimensioni cambiano. Ciò può causare problemi di prestazioni e salti dell'interfaccia utente quando i contenuti sono complessi. - Le dimensioni disponibili potrebbero variare a seconda dell'implementazione del launcher. Ad esempio, se il launcher non fornisce l'elenco delle dimensioni, viene utilizzata la dimensione minima possibile.
- Nei dispositivi con versioni precedenti ad Android 12, la logica di calcolo delle dimensioni potrebbe non funzionare in tutte le situazioni.
In generale, devi utilizzare questa modalità se non è possibile utilizzare 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 gli ID risorsa direttamente per ridurre le dimensioni dell'oggetto
RemoteViews finale e per attivare risorse dinamiche, come i colori
dinamici.
I composable e i metodi accettano le risorse utilizzando un "provider", ad esempio
ImageProvider, o un metodo di overload 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 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"
)
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 cliccabile che rappresenta lo stato "selezionato".
var isApplesChecked by remember { mutableStateOf(false) } var isEnabledSwitched by remember { mutableStateOf(false) } var isRadioChecked by remember { mutableIntStateOf(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 lambda fornita. Puoi memorizzare 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 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 |
|
Componente | |
| Pulsanti con contorni |
|
Componente | |
| Pulsanti icona |
|
Componente | Principale / Secondario / Solo icona |
| Barra del titolo |
|
Componente | |
| Scaffold | La struttura e la barra del titolo si trovano nella stessa demo. |
Per ulteriori informazioni sulle specifiche di progettazione, consulta i progetti dei componenti in questo kit di progettazione su Figma.
Per ulteriori informazioni sui layout canonici, visita la pagina Layout canonici dei widget.