Nesta página, descrevemos como lidar com tamanhos e fornecer layouts flexíveis e responsivos com o Glance usando componentes dele.
Use Box, Column e Row
O Glance tem três layouts combináveis principais:
Box: coloca elementos um em cima do outro. Isso se traduz em umRelativeLayout.Column: coloca elementos um após o outro no eixo vertical. Ela é traduzida para umLinearLayoutcom orientação vertical.Row: coloca os elementos um após o outro no eixo horizontal. Ela é traduzida para umLinearLayoutcom orientação horizontal.
O Glance é compatível com objetos Scaffold. Coloque os elementos combináveis Column, Row e
Box em um determinado objeto Scaffold.
Cada um desses elementos combináveis permite definir os alinhamentos vertical e horizontal do conteúdo e as restrições de largura, altura, peso ou padding usando modificadores. Além disso, cada filho pode definir o modificador para mudar o espaço e o posicionamento dentro do elemento pai.
O exemplo a seguir mostra como criar um Row que distribui os filhos de maneira uniforme na horizontal, como mostrado na Figura 1:
Row(modifier = GlanceModifier.fillMaxWidth().padding(16.dp)) { val modifier = GlanceModifier.defaultWeight() Text("first", modifier) Text("second", modifier) Text("third", modifier) }
O Row preenche a largura máxima disponível e, como cada filho tem a mesma ponderação, eles compartilham o espaço disponível de maneira uniforme. É possível definir diferentes pesos, tamanhos, paddings ou alinhamentos para adaptar os layouts às suas necessidades.
Usar layouts roláveis
Outra maneira de fornecer conteúdo responsivo é torná-lo rolável. Isso é
possível com o elemento combinável LazyColumn. Com esse elemento combinável, é possível definir um conjunto
de itens a serem mostrados em um contêiner rolável no widget do app.
Os snippets a seguir mostram diferentes maneiras de definir itens dentro do
LazyColumn.
Você pode informar o número de itens:
// Remember to import Glance Composables // import androidx.glance.appwidget.layout.LazyColumn LazyColumn { items(10) { index: Int -> Text( text = "Item $index", modifier = GlanceModifier.fillMaxWidth() ) } }
Forneça itens individuais:
LazyColumn { item { Text("First Item") } item { Text("Second Item") } }
Forneça uma lista ou matriz de itens:
LazyColumn { items(peopleNameList) { name -> Text(name) } }
Também é possível usar uma combinação dos exemplos 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") } }
O snippet anterior não especifica o itemId. Especificar o
itemId ajuda a melhorar o desempenho e manter a posição de rolagem
em atualizações de lista e appWidget do Android 12 em diante (por
exemplo, ao adicionar ou remover itens da lista). O exemplo a seguir mostra como especificar um itemId:
items(items = peopleList, itemId = { person -> person.id.hashCode().toLong() }) { person -> Text(person.name) }
Rolagem com ajuste
A rolagem de ajuste é uma animação que permite que o conteúdo rolável se ajuste à parte de cima do contêiner de widget.
Para implementar a rolagem de ajuste, verifique se você atende às seguintes condições:
- Atualize a dependência do Glance para a versão 1.3.0-alpha02 ou mais recente.
- Defina seu
compileSdkcomo 37 ou mais, já que a rolagem de ajuste é compatível com dispositivos que executam o Android 17 e versões mais recentes. - Configure seu
LazyColumncomVerticalScrollMode. Se o dispositivo for compatível com rolagem por ajuste, useSnapScrollMatchHeight. Do contrário, useNormal.
Se você estiver usando a rolagem de ajuste com imagens, consulte o layout canônico de imagem sangrada.
@Composable fun SnapScrollLayout() { val height = LocalSize.current.height val items = listOf( ColorItem(Color.Red, "Red"), ColorItem(Color.Yellow, "Yellow"), ColorItem(Color.Blue, "Blue") ) if (Build.VERSION.SDK_INT >= 36 && isSnapScrollSupported) { LazyColumn( verticalScrollMode = VerticalScrollMode.SnapScrollMatchHeight(height) ) { items(items) { item -> ColorCard(item, height) } } } else { LazyColumn { 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) ) } } val isSnapScrollSupported: Boolean get() = Build.VERSION.SDK_INT >= 36 && Build.VERSION.SDK_INT_FULL >= Build.VERSION_CODES_FULL.BAKLAVA_1
Definir o SizeMode
Os tamanhos dos AppWidget podem variar dependendo do dispositivo, da escolha do usuário ou do iniciador.
Por isso, é importante fornecer layouts flexíveis, conforme descrito na página Fornecer
layouts de widgets flexíveis. O Glance simplifica isso com a definição SizeMode e o valor LocalSize. As seções a seguir descrevem os três
modos.
SizeMode.Single
SizeMode.Single é o modo padrão. Isso indica que apenas um tipo de conteúdo é fornecido. Ou seja, mesmo que o tamanho disponível do AppWidget mude, o tamanho do conteúdo não será alterado.
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 // ... } }
Ao usar esse modo, verifique se:
- Os valores mínimos e máximos de metadados de tamanho são definidos corretamente com base no tamanho do conteúdo.
- O conteúdo é flexível o suficiente dentro do intervalo de tamanho esperado.
Em geral, use esse modo quando:
a) o AppWidget tem um tamanho fixo ou
b) não muda o conteúdo quando é redimensionado.
SizeMode.Responsive
Esse modo é o equivalente a fornecer layouts responsivos, o que permite que o GlanceAppWidget defina um conjunto de layouts responsivos limitados por tamanhos específicos. Para cada tamanho definido, o conteúdo é criado e mapeado para o tamanho específico quando o AppWidget é criado ou atualizado. Em seguida, o sistema seleciona o mais adequado com base no tamanho disponível.
Por exemplo, no nosso destino AppWidget, você pode definir três tamanhos e o conteúdo deles:
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") } } } }
No exemplo anterior, o método provideContent é chamado três vezes e
mapeado para o tamanho definido.
- Na primeira chamada, o tamanho é avaliado como
100x100. O conteúdo não inclui o botão extra nem os textos de cima e de baixo. - Na segunda chamada, o tamanho é avaliado como
250x100. O conteúdo inclui o botão extra, mas não os textos de cima e de baixo. - Na terceira chamada, o tamanho é avaliado como
250x250. O conteúdo inclui o botão extra e os dois textos.
SizeMode.Responsive é uma combinação dos outros dois modos e permite definir conteúdo responsivo dentro de limites predefinidos. Em geral, esse modo tem melhor desempenho e permite transições mais suaves quando o AppWidget é redimensionado.
A tabela a seguir mostra o valor do tamanho, dependendo do SizeMode e do tamanho disponível do AppWidget:
| Tamanho disponível | 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 |
| * Os valores exatos são apenas para fins de demonstração. |
SizeMode.Exact
SizeMode.Exact é o equivalente a fornecer layouts exatos, que
solicita o conteúdo GlanceAppWidget sempre que o tamanho AppWidget disponível
muda (por exemplo, quando o usuário redimensiona o AppWidget na tela inicial).
Por exemplo, no widget de destino, um botão extra pode ser adicionado se a largura disponível for maior que um determinado valor.
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") } } } } }
Esse modo oferece mais flexibilidade do que os outros, mas tem algumas ressalvas:
- O
AppWidgetprecisa ser recriado completamente sempre que o tamanho mudar. Isso pode causar problemas de desempenho e saltos na interface quando o conteúdo é complexo. - O tamanho disponível pode variar dependendo da implementação do iniciador. Por exemplo, se o iniciador não fornecer a lista de tamanhos, o tamanho mínimo possível será usado.
- Em dispositivos com versões anteriores ao Android 12, a lógica de cálculo de tamanho pode não funcionar em todas as situações.
Em geral, use esse modo se SizeMode.Responsive não puder ser usado
(ou seja, um pequeno conjunto de layouts responsivos não é viável).
Acessar recursos
Use LocalContext.current para acessar qualquer recurso do Android, conforme mostrado no
exemplo a seguir:
LocalContext.current.getString(R.string.glance_title)
Recomendamos fornecer IDs de recursos diretamente para reduzir o tamanho do objeto RemoteViews final e ativar recursos dinâmicos, como cores dinâmicas.
Combináveis e métodos aceitam recursos usando um "provedor", como
ImageProvider, ou um método de sobrecarga, como
GlanceModifier.background(R.color.blue). Exemplo:
Column( modifier = GlanceModifier.background(R.color.default_widget_background) ) { /**...*/ } Image( provider = ImageProvider(R.drawable.ic_logo), contentDescription = "My image", )
Manipular texto
O Glance 1.1.0 inclui uma API para definir seus estilos de texto. Defina estilos de texto usando os atributos
fontSize, fontWeight ou fontFamily da classe TextStyle.
O fontFamily é compatível com todas as fontes do sistema, conforme mostrado no exemplo a seguir, mas
fontes personalizadas em apps não são compatíveis:
Text(
style = TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 18.sp,
fontFamily = FontFamily.Monospace
),
text = "Example Text"
)
Adicionar botões compostos
Os botões compostos foram introduzidos no Android 12. O Glance é compatível com a compatibilidade com versões anteriores para os seguintes tipos de botões compostos:
Cada um desses botões compostos mostra uma visualização clicável que representa o estado "marcado".
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 o estado muda, a lambda fornecida é acionada. Você pode armazenar o estado da caixa de seleção, conforme mostrado no exemplo a seguir:
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) } ) } }
Você também pode fornecer o atributo colors para CheckBox, Switch e RadioButton para personalizar as cores:
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 adicionais
O Glance 1.1.0 inclui o lançamento de componentes adicionais, conforme descrito na tabela a seguir:
| Nome | Imagem | Link de referência | Outras observações |
|---|---|---|---|
| Botão preenchido |
|
Component | |
| Botões de contorno |
|
Component | |
| Botões de ícone |
|
Component | Principal / Secundário / Somente ícone |
| Barra de título |
|
Component | |
| Scaffold | O scaffold e a barra de título estão na mesma demonstração. |
Para mais informações sobre especificações de design, consulte os designs de componentes neste kit de design no Figma.
Para mais informações sobre layouts canônicos, acesse Layouts de widgets canônicos.