Criar uma interface com o Glance

Esta página descreve como lidar com tamanhos e fornecer recursos flexíveis e layouts com o Glance, usando componentes atuais do Glance.

Usar Box, Column e Row

O Glance tem três layouts principais combináveis:

  • Box: posiciona elementos sobre outros. Ele é traduzido como um RelativeLayout.

  • Column: posiciona os elementos um após o outro no eixo vertical. Ela traduz para uma LinearLayout com orientação vertical.

  • Row: posiciona os elementos um após o outro no eixo horizontal. Ela traduz para uma LinearLayout com orientação horizontal.

O Glance oferece suporte a objetos Scaffold. Coloque seus Column, Row e Elementos combináveis Box em um determinado objeto Scaffold.

Imagem de um layout de coluna, linha e caixa.
Figura 1. Exemplos de layouts com Column, Row e Box.
.

Cada um desses elementos combináveis permite definir os alinhamentos vertical e horizontal. do conteúdo e das restrições de largura, altura, peso ou padding usando modificadores. Além disso, cada filho pode definir seu próprio modificador para mudar o espaço e posicionamento dentro do principal.

O exemplo a seguir mostra como criar um Row que distribui de forma uniforme os filhos horizontalmente, como visto 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 a mesma peso, eles compartilham igualmente o espaço disponível. É possível definir pesos diferentes, 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. 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 a seguir mostram maneiras diferentes de definir itens dentro da 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. Especificar o O itemId ajuda a melhorar o desempenho e manter a rolagem. posição em listas e atualizações de 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, 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, como descrito em Fornecer com layouts de widget flexíveis. O Glance simplifica isso com SizeMode e o valor LocalSize. As seções a seguir descrevem os três dois modos.

SizeMode.Single

SizeMode.Single é o modo padrão. Isso indica que apenas um tipo conteúdo for fornecido; ou seja, mesmo que o tamanho disponível de AppWidget mude, o tamanho do conteúdo não é 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) o AppWidget tiver um tamanho fixo; b) o conteúdo não seja alterado quando redimensionado.

SizeMode.Responsive

Esse modo é o equivalente a fornecer layouts responsivos, o que permite a GlanceAppWidget para definir um conjunto de layouts responsivos limitados por medidas tamanhos. Para cada tamanho definido, o conteúdo é criado e mapeado para o tamanho quando a AppWidget é criada ou atualizada. Em seguida, o sistema seleciona o melhor ajuste, aquele com base no tamanho disponível.

Por exemplo, no AppWidget de destino, é possível definir três tamanhos e conteúdo:

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 incluem o botão extra nem os textos das partes de cima e de baixo.
  • Na segunda chamada, o tamanho é avaliado como 250x100. O conteúdo inclui 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 botão extra e ambos os textos.

O SizeMode.Responsive é uma combinação dos outros dois modos e permite definir o conteúdo responsivo dentro de limites predefinidos. Em geral, esse modo tem melhor desempenho e permite transições mais suaves quando a AppWidget é redimensionada.

A tabela a seguir mostra o valor do tamanho, dependendo do SizeMode e o tamanho disponível de 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 o largura disponível é maior do 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 alguns ressalvas:

  • O AppWidget precisa ser totalmente recriado toda vez que o tamanho mudar. Isso pode levar a problemas de desempenho 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 mínimo tamanho possível é usado.
  • Em dispositivos anteriores ao Android 12, a lógica de cálculo de tamanho pode não funcionar em todos em diferentes situações.

Em geral, use esse modo se não for possível usar SizeMode.Responsive. 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 arquivo final RemoteViews e para ativar recursos dinâmicos, como dynamic cores.

Os elementos combináveis e métodos aceitam recursos usando um "provedor", como ImageProvider ou usando 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",
)

Processar texto

O Glance 1.1.0 inclui uma API para definir estilos de texto. Definir estilos de texto usando Atributos fontSize, fontWeight ou fontFamily da classe TextStyle.

fontFamily oferece suporte a todas as fontes do sistema, como mostrado no exemplo abaixo, 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 lançados no Android 12. O Glance oferece suporte para versões anteriores compatibilidade para os seguintes tipos de botões compostos:

Cada botão composto exibe uma visualização clicável que representa "marcado" state.

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 verificar o estado, como 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) }
        )
    }
}

Também é possível 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 no tabela a seguir:

Nome Imagem Link de referência Outras observações
Botão preenchido alt_text Componente
Botões de contorno alt_text Componente
Botões de ícone alt_text Componente Principal / Secundário / Apenas ícones
Barra do título alt_text Componente
Scaffold Scaffold e barra de título estão na mesma demonstração.