En esta página, se describe cómo controlar los tamaños y proporcionar diseños flexibles y responsivos con Glance, usando los componentes existentes de Glance.
Usa Box
, Column
y Row
Glance tiene tres diseños componibles principales:
Box
: Coloca elementos uno sobre otro. Se traduce a unRelativeLayout
.Column
: Coloca los elementos uno tras otro en el eje vertical. Se traduce a unLinearLayout
con orientación vertical.Row
: Coloca los elementos uno tras otro en el eje horizontal. Se traduce a unLinearLayout
con orientación horizontal.
Glance admite objetos Scaffold
. Coloca tus elementos Column
, Row
y Box
componibles dentro de un objeto Scaffold
determinado.
Cada uno de estos elementos componibles te permite definir las alineaciones verticales y horizontales de su contenido y las restricciones de ancho, altura, peso o padding con modificadores. Además, cada elemento secundario puede definir su modificador para cambiar el espacio y la ubicación dentro del elemento superior.
En el siguiente ejemplo, se muestra cómo crear un Row
que distribuya de manera uniforme
sus elementos secundarios horizontalmente, como se ve en la Figura 1:
Row(modifier = GlanceModifier.fillMaxWidth().padding(16.dp)) { val modifier = GlanceModifier.defaultWeight() Text("first", modifier) Text("second", modifier) Text("third", modifier) }
El Row
completa el ancho máximo disponible y, como cada elemento secundario tiene el mismo peso, comparten el espacio disponible de manera uniforme. Puedes definir diferentes grosores, tamaños, rellenos o alineaciones para adaptar los diseños a tus necesidades.
Usa diseños desplazables
Otra forma de proporcionar contenido responsivo es hacerlo desplazable. Esto es posible con el elemento LazyColumn
componible. Este elemento componible te permite definir un conjunto de elementos que se mostrarán dentro de un contenedor desplazable en el widget de la app.
En los siguientes fragmentos, se muestran diferentes formas de definir elementos dentro de LazyColumn
.
Puedes proporcionar la cantidad de elementos:
// Remember to import Glance Composables // import androidx.glance.appwidget.layout.LazyColumn LazyColumn { items(10) { index: Int -> Text( text = "Item $index", modifier = GlanceModifier.fillMaxWidth() ) } }
Proporciona elementos individuales:
LazyColumn { item { Text("First Item") } item { Text("Second Item") } }
Proporciona una lista o un array de elementos:
LazyColumn { items(peopleNameList) { name -> Text(name) } }
También puedes usar una combinación de los ejemplos anteriores:
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") } }
Ten en cuenta que el fragmento anterior no especifica itemId
. Especificar itemId
ayuda a mejorar el rendimiento y a mantener la posición de desplazamiento a través de actualizaciones de lista y appWidget
a partir de Android 12 (por ejemplo, cuando se agregan o quitan elementos de la lista). En el siguiente ejemplo, se muestra cómo especificar un itemId
:
items(items = peopleList, key = { person -> person.id }) { person -> Text(person.name) }
Define el SizeMode
Los tamaños de AppWidget
pueden diferir según el dispositivo, la elección del usuario o el selector, por lo que es importante proporcionar diseños flexibles, como se describe en la página Proporciona diseños de widgets flexibles. Glance simplifica esto con la definición de SizeMode
y el valor de LocalSize
. En las siguientes secciones, se describen los tres modos.
SizeMode.Single
SizeMode.Single
es el modo predeterminado. Indica que solo se proporciona un tipo de contenido, es decir, incluso si cambia el tamaño disponible de AppWidget
, no cambia el tamaño del contenido.
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 // ... } }
Cuando uses este modo, asegúrate de lo siguiente:
- Los valores de metadatos de tamaño mínimo y máximo se definen correctamente según el tamaño del contenido.
- El contenido es lo suficientemente flexible dentro del rango de tamaño esperado.
En general, debes usar este modo en los siguientes casos:
a) AppWidget
tiene un tamaño fijo o b) no cambia su contenido cuando se cambia de tamaño.
SizeMode.Responsive
Este modo equivale a proporcionar diseños responsivos, lo que permite que GlanceAppWidget
defina un conjunto de diseños responsivos delimitados por tamaños específicos. Para cada tamaño definido, el contenido se crea y se asigna al tamaño específico cuando se crea o actualiza AppWidget
. Luego, el sistema selecciona el que mejor se adapte según el tamaño disponible.
Por ejemplo, en nuestro AppWidget
de destino, puedes definir tres tamaños y su contenido:
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") } } } }
En el ejemplo anterior, se llama al método provideContent
tres veces y se asigna al tamaño definido.
- En la primera llamada, el tamaño se evalúa como
100x100
. El contenido no incluye el botón adicional ni los textos de la parte superior e inferior. - En la segunda llamada, el tamaño se evalúa como
250x100
. El contenido incluye el botón adicional, pero no los textos de la parte superior ni inferior. - En la tercera llamada, el tamaño se evalúa como
250x250
. El contenido incluye el botón adicional y ambos textos.
SizeMode.Responsive
es una combinación de los otros dos modos y te permite definir contenido responsivo dentro de límites predefinidos. En general, este modo tiene un mejor rendimiento y permite transiciones más fluidas cuando se cambia el tamaño de AppWidget
.
En la siguiente tabla, se muestra el valor del tamaño, según el tamaño disponible de SizeMode
y AppWidget
:
Tamaño disponible | 105 × 110 | 203 × 112 | 72 x 72 | 203 x 150 |
---|---|---|---|---|
SizeMode.Single |
110 x 110 | 110 x 110 | 110 x 110 | 110 x 110 |
SizeMode.Exact |
105 × 110 | 203 × 112 | 72 x 72 | 203 x 150 |
SizeMode.Responsive |
80 x 100 | 80 x 100 | 80 x 100 | 150 x 120 |
* Los valores exactos son solo para fines de demostración. |
SizeMode.Exact
SizeMode.Exact
es el equivalente de proporcionar diseños exactos, que solicita el contenido de GlanceAppWidget
cada vez que cambia el tamaño disponible de AppWidget
(por ejemplo, cuando el usuario cambia el tamaño de AppWidget
en la pantalla principal).
Por ejemplo, en el widget de destino, se puede agregar un botón adicional si el ancho disponible es mayor que un valor determinado.
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") } } } } }
Este modo proporciona más flexibilidad que los demás, pero tiene algunas salvedades:
- El
AppWidget
se debe volver a crear por completo cada vez que cambie el tamaño. Esto puede generar problemas de rendimiento y saltos de la IU cuando el contenido es complejo. - El tamaño disponible puede variar según la implementación del selector. Por ejemplo, si el selector no proporciona la lista de tamaños, se usa el tamaño mínimo posible.
- En dispositivos anteriores a Android 12, es posible que la lógica de cálculo del tamaño no funcione en todas las situaciones.
En general, debes usar este modo si no se puede usar SizeMode.Responsive
(es decir, no es factible un conjunto pequeño de diseños responsivos).
Cómo acceder a recursos
Usa LocalContext.current
para acceder a cualquier recurso de Android, como se muestra en el siguiente ejemplo:
LocalContext.current.getString(R.string.glance_title)
Te recomendamos que proporciones los IDs de recursos directamente para reducir el tamaño del objeto RemoteViews
final y habilitar recursos dinámicos, como los colores dinámicos.
Los elementos componibles y los métodos aceptan recursos con un "proveedor", como ImageProvider
, o con un método de sobrecarga, como GlanceModifier.background(R.color.blue)
. Por ejemplo:
Column( modifier = GlanceModifier.background(R.color.default_widget_background) ) { /**...*/ } Image( provider = ImageProvider(R.drawable.ic_logo), contentDescription = "My image", )
Cómo controlar el texto
Glance 1.1.0 incluye una API para establecer tus estilos de texto. Establece estilos de texto con los atributos fontSize
, fontWeight
o fontFamily
de la clase TextStyle.
fontFamily
admite todas las fuentes del sistema, como se muestra en el siguiente ejemplo, pero no las fuentes personalizadas en las apps:
Text(
style = TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 18.sp,
fontFamily = FontFamily.Monospace
),
text = "Example Text"
)
Cómo agregar botones compuestos
Los botones compuestos se presentaron en Android 12. Glance admite la retrocompatibilidad con los siguientes tipos de botones compuestos:
Cada uno de estos botones compuestos muestra una vista en la que se puede hacer clic que representa el estado “marcado”.
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" )
Cuando cambia el estado, se activa la expresión lambda proporcionada. Puedes almacenar el estado de verificación, como se muestra en el siguiente ejemplo:
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) } ) } }
También puedes proporcionar el atributo colors
a CheckBox
, Switch
y RadioButton
para personalizar sus colores:
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) ), )
Componentes adicionales
Glance 1.1.0 incluye la versión de componentes adicionales, como se describe en la siguiente tabla:
Name | Imagen | Vínculo de referencia | Notas adicionales |
---|---|---|---|
Botón con relleno | Componente | ||
Botones con contorno | Componente | ||
Botones de ícono | Componente | Principal / secundario / solo ícono | |
Barra del título | Componente | ||
Scaffold | El andamiaje y la barra del título están en la misma demostración. |
Para obtener más información sobre los detalles del diseño, consulta los diseños de componentes en este kit de diseño en Figma.
Para obtener más información sobre los diseños canónicos, consulta Diseños de widgets canónicos.