Esta página descreve como processar tamanhos e fornecer layouts flexíveis e responsivos com o Glance, usando componentes existentes desse recurso.
Usar Box
, Column
e Row
O Glance tem três layouts principais combináveis:
Box
: posiciona elementos sobre outros. Ele é traduzido como umRelativeLayout
.Column
: posiciona os elementos um após o outro no eixo vertical. Ele se traduz em umaLinearLayout
com orientação vertical.Row
: posiciona os elementos um após o outro no eixo horizontal. Ele se traduz em umaLinearLayout
com orientação horizontal.
O Glance oferece suporte a 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 próprio modificador para mudar o espaço e a posição dentro do pai.
O exemplo abaixo mostra como criar um Row
que distribua 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) }
A Row
preenche a largura máxima disponível e, como cada filho tem o mesmo
peso, eles compartilham uniformemente o espaço disponível. Você pode definir diferentes pesos,
tamanhos, padding 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
. Esse elemento combinável permite definir um conjunto
de itens a serem mostrados dentro de um contêiner rolável no widget do app.
Os snippets abaixo mostram maneiras diferentes de definir itens dentro do
LazyColumn
.
Você pode fornecer 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
. A especificação do
itemId
ajuda a melhorar o desempenho e manter a posição
de rolagem usando atualizações de lista e appWidget
do Android 12 e versões mais recentes (por
exemplo, ao adicionar ou remover itens da lista). O exemplo abaixo
mostra como especificar um itemId
:
items(items = peopleList, key = { person -> person.id }) { person -> Text(person.name) }
Definir o SizeMode
Os tamanhos de AppWidget
podem variar dependendo do dispositivo, da escolha do usuário ou da tela de início.
Por isso, é importante fornecer layouts flexíveis, conforme descrito na página Oferecer
layouts de widget flexíveis. O Glance simplifica isso com a definição de 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 de AppWidget
disponível 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 de metadados de tamanho mínimo e máximo são definidos adequadamente com base no tamanho do conteúdo.
- O conteúdo é flexível o suficiente dentro do intervalo de tamanho esperado.
Em geral, você deve usar esse modo quando:
a) a AppWidget
tiver um tamanho fixo ou
b) não mudar o conteúdo quando redimensionada.
SizeMode.Responsive
Esse modo é equivalente a fornecer layouts responsivos, que permite
que o GlanceAppWidget
defina um conjunto de layouts responsivos delimitados por tamanhos
específicos. Para cada tamanho definido, o conteúdo é criado e mapeado para o tamanho
específico quando a AppWidget
é criada ou atualizada. Em seguida, o sistema seleciona o
melhor ajuste com base no tamanho disponível.
Por exemplo, em 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
associado ao 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 um desempenho melhor e permite transições mais suaves quando a AppWidget
é redimensionada.
A tabela a seguir mostra o valor do tamanho, dependendo do tamanho disponível de SizeMode
e
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 de 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 poderá 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 que os outros, mas vem com algumas ressalvas:
- O
AppWidget
precisa ser totalmente recriado toda vez que o tamanho mudar. Isso pode levar a problemas de performance e saltos na interface quando o conteúdo é complexo. - O tamanho disponível pode ser diferente dependendo da implementação do inicializador. Por exemplo, se a tela de início não fornecer a lista de tamanhos, o tamanho mínimo possível será usado.
- Em dispositivos anteriores ao Android 12, a lógica de cálculo de tamanho pode não funcionar em todas as situações.
Em geral, você precisará usar 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 abaixo:
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.
Os elementos combináveis e os métodos aceitam recursos usando um "provedor", como
ImageProvider
, ou um método de sobrecarga, como
GlanceModifier.background(R.color.blue)
. Por exemplo:
Column( modifier = GlanceModifier.background(R.color.default_widget_background) ) { /**...*/ } Image( provider = ImageProvider(R.drawable.ic_logo), contentDescription = "My image", )
Processar texto
O Glance 1.1.0 inclui uma API para definir estilos de texto. Defina estilos de texto usando
os atributos fontSize
, fontWeight
ou fontFamily
da classe TextStyle.
fontFamily
oferece suporte a todas as fontes do sistema, conforme mostrado no exemplo abaixo, mas
fontes personalizadas 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 lançados no Android 12. O Glance oferece suporte à compatibilidade com versões anteriores destes tipos de botões compostos:
Cada botão composto exibe 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 { 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 o estado muda, o lambda fornecido é acionado. É possível armazenar o estado da verificaçã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) ), )
Outros componentes
O Glance 1.1.0 inclui o lançamento de outros componentes, conforme descrito na tabela abaixo:
Nome | Imagem | Link de referência | Outras observações |
---|---|---|---|
Botão preenchido | Componente | ||
Botões de contorno | Componente | ||
Botões de ícone | Componente | Principal / Secundário / Apenas ícones | |
Barra de título | Componente | ||
Scaffold | Scaffold e barra de título estão na mesma demonstração. |
Para saber mais sobre detalhes específicos de design, consulte os designs de componentes neste kit de design (link em inglês) no Figma.