Criar um widget de app com o Glance

manifesto, metadados

As seções a seguir descrevem como criar um widget de app básico com o Glance.

Declarar o AppWidget no manifesto

Depois de concluir as etapas de configuração, declare o AppWidget e os metadados dele no app.

  1. Estenda o receptor AppWidget de GlanceAppWidgetReceiver:

    class MyAppWidgetReceiver : GlanceAppWidgetReceiver() {
        override val glanceAppWidget: GlanceAppWidget = TODO("Create GlanceAppWidget")
    }

  2. Registre o provedor do widget de app no arquivo AndroidManifest.xml e no arquivo de metadados associado:

        <receiver android:name=".glance.MyReceiver"
        android:exported="true">
        <intent-filter>
            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
        </intent-filter>
        <meta-data
            android:name="android.appwidget.provider"
            android:resource="@xml/my_app_widget_info" />
    </receiver>
    

Adicionar os metadados AppWidgetProviderInfo

Em seguida, siga o guia Criar um widget para criar e definir as informações do widget de app no arquivo @xml/my_app_widget_info.

A única diferença para o Glance é que não há XML initialLayout, mas você precisa definir um. Você pode usar o layout de carregamento predefinido fornecido na biblioteca:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/glance_default_loading_layout">
</appwidget-provider>

Declarar o XML AppWidgetProviderInfo

O objeto AppWidgetProviderInfo define as qualidades essenciais do seu widget. Defina o AppWidgetProviderInfo no arquivo de recurso de metadados XML (res/xml/my_app_widget_info.xml) dentro de um elemento <appwidget-provider>:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="40dp"
    android:minHeight="40dp"
    android:targetCellWidth="1"
    android:targetCellHeight="1"
    android:maxResizeWidth="250dp"
    android:maxResizeHeight="120dp"
    android:updatePeriodMillis="86400000"
    android:description="@string/example_appwidget_description"
    android:previewLayout="@layout/example_appwidget_preview"
    android:initialLayout="@layout/glance_default_loading_layout"
    android:configure="com.example.android.ExampleAppWidgetConfigurationActivity"
    android:resizeMode="horizontal|vertical"
    android:widgetCategory="home_screen"
    android:widgetFeatures="reconfigurable|configuration_optional">
</appwidget-provider>

Atributos de dimensionamento de widgets

A tela inicial padrão posiciona widgets na janela com base em uma grade de células com altura e largura definidas. A maioria das telas iniciais só permite que os widgets assumam tamanhos que são múltiplos inteiros das células da grade. Por exemplo, duas células na horizontal por três na vertical.

Com os atributos de dimensionamento de widget, é possível especificar um tamanho padrão para o widget e fornecer limites inferiores e superiores para o tamanho dele. Nesse contexto, o tamanho padrão de um widget é o tamanho que ele assume quando é adicionado à tela inicial pela primeira vez.

A tabela a seguir descreve os atributos <appwidget-provider> relacionados ao dimensionamento de widgets:

Atributos e descrição
targetCellWidth e targetCellHeight (Android 12), minWidth e minHeight
  • A partir do Android 12, os atributos targetCellWidth e targetCellHeight especificam o tamanho padrão do widget em termos de células de grade. Esses atributos são ignorados no Android 11 e em versões anteriores e podem ser ignorados se a tela inicial não for compatível com um layout baseado em grade.
  • Os atributos minWidth e minHeight especificam o tamanho padrão do widget em dp. Se os valores da largura ou da altura mínima de um widget não corresponderem às dimensões das células, eles serão arredondados para cima até o tamanho de célula mais próximo.
Recomendamos especificar os dois conjuntos de atributos: targetCellWidth e targetCellHeight, e minWidth e minHeight. Assim, seu app pode voltar a usar minWidth e minHeight se o dispositivo do usuário não for compatível com targetCellWidth e targetCellHeight. Se compatíveis, os atributos targetCellWidth e targetCellHeight terão precedência sobre os atributos minWidth e minHeight.
minResizeWidth e minResizeHeight Especifique o tamanho mínimo absoluto do widget. Esses valores especificam o tamanho abaixo do qual o widget fica ilegível ou inutilizável. Usar esses atributos permite que o usuário redimensione o widget para um tamanho menor que o padrão. O atributo minResizeWidth é ignorado se for maior que minWidth ou se o redimensionamento horizontal não estiver ativado. Consulte resizeMode. Da mesma forma, o atributo minResizeHeight é ignorado se for maior que minHeight ou se o redimensionamento vertical não estiver ativado.
maxResizeWidth e maxResizeHeight Especifique o tamanho máximo recomendado do widget. Se os valores não forem múltiplos das dimensões da célula da grade, eles serão arredondados para cima até o tamanho de célula mais próximo. O atributo maxResizeWidth é ignorado se for menor que minWidth ou se o redimensionamento horizontal não estiver ativado. Consulte resizeMode. Da mesma forma, o atributo maxResizeHeight será ignorado se for menor que minHeight ou se o redimensionamento vertical não estiver ativado. Introduzido no Android 12.
resizeMode Especifica as regras pelas quais um widget pode ser redimensionado. É possível usar esse atributo para permitir o redimensionamento dos widgets da tela inicial na horizontal, vertical ou nos dois eixos. Os usuários tocam e pressionam um widget para mostrar as alças de redimensionamento e arrastam as alças horizontais ou verticais para mudar o tamanho na grade de layout. Os valores para o atributo resizeMode incluem horizontal, vertical e none. Para declarar um widget como redimensionável na horizontal e na vertical, use horizontal|vertical.

Exemplo

Para ilustrar como os atributos na tabela anterior afetam o dimensionamento do widget, considere as seguintes especificações:

  • Uma célula da grade tem 30 dp de largura e 50 dp de altura.
  • A seguinte especificação de atributo é fornecida:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="80dp"
    android:minHeight="80dp"
    android:targetCellWidth="2"
    android:targetCellHeight="2"
    android:minResizeWidth="40dp"
    android:minResizeHeight="40dp"
    android:maxResizeWidth="120dp"
    android:maxResizeHeight="120dp"
    android:resizeMode="horizontal|vertical" />

A partir do Android 12:

Use os atributos targetCellWidth e targetCellHeight como o tamanho padrão do widget.

O tamanho padrão do widget é 2x2. O widget pode ser redimensionado para baixo até 2x1 ou para cima até 4x3.

Android 11 e versões anteriores:

Use os atributos minWidth e minHeight para calcular o tamanho padrão do widget.

A largura padrão = Math.ceil(80 / 30) = 3

A altura padrão = Math.ceil(80 / 50) = 2

O tamanho padrão do widget é 3x2. O widget pode ser redimensionado para 2x1 ou até tela cheia.

Outros atributos do widget

A tabela a seguir descreve os atributos <appwidget-provider> relacionados a qualidades que não sejam o dimensionamento de widgets.

Atributos e descrição
updatePeriodMillis Define a frequência com que o framework de widget solicita uma atualização do GlanceAppWidgetReceiver chamando o método de callback onUpdate(). Recomendamos atualizar com a menor frequência possível, no máximo uma vez por hora, para conservar a bateria. Para mais detalhes, consulte a seção Quando atualizar widgets em Gerenciamento de estado do Glance.
initialLayout Aponta para o recurso de layout que define o layout de carregamento do widget antes da renderização das composições da interface do Glance. Você pode usar o layout de carregamento predefinido fornecido na biblioteca: @layout/glance_default_loading_layout.
configure Define a atividade de configuração que é iniciada quando o usuário adiciona o widget. Consulte a seção Implementar uma atividade de configuração de widget nesta página.
description Especifica a descrição que o seletor de widgets vai mostrar para seu widget. Introduzido no Android 12.
previewLayout (Android 12) e previewImage (Android 11 e versões anteriores)
  • A partir do Android 12, o atributo previewLayout especifica uma visualização escalonável, que você fornece como um layout XML definido para o tamanho padrão do widget. O ideal é que ele aponte para um mapeamento XML estático que corresponda ao layout do seu design.
  • No Android 11 ou versões anteriores, o atributo previewImage especifica uma captura de tela estática do drawable de imagem de como o widget aparece no seletor de widgets.
Recomendamos especificar os dois para que o app faça um fallback adequado em plataformas mais antigas. Para plataformas mais recentes (Android 15 ou mais recente), é possível definir prévias geradas dinamicamente em Kotlin usando `GlanceAppWidget.providePreview`. Consulte o guia de prévias geradas.
autoAdvanceViewId Especifica o ID de visualização da subexibição do widget que é avançada automaticamente pelo host do widget.
widgetCategory Declara se o widget pode ser exibido na tela inicial (home_screen), na tela de bloqueio (keyguard) ou nas duas. Para o Android 5.0 e versões posteriores, apenas home_screen é válido.
widgetFeatures Declara os recursos compatíveis com o widget. Por exemplo, se a configuração do widget for opcional, especifique configuration_optional e reconfigurable.

Definir GlanceAppWidget

  1. Crie uma classe que estenda GlanceAppWidget e substitua o método provideGlance. Este é o método em que você pode carregar os dados necessários para renderizar seu widget:

    class MyAppWidget : GlanceAppWidget() {
    
        override suspend fun provideGlance(context: Context, id: GlanceId) {
    
            // In this method, load data needed to render the AppWidget.
            // Use `withContext` to switch to another thread for long running
            // operations.
    
            provideContent {
                // create your AppWidget here
                Text("Hello World")
            }
        }
    }

  2. Instancie-o no glanceAppWidget no seu GlanceAppWidgetReceiver:

    class MyAppWidgetReceiver : GlanceAppWidgetReceiver() {
    
        // Let MyAppWidgetReceiver know which GlanceAppWidget to use
        override val glanceAppWidget: GlanceAppWidget = MyAppWidget()
    }

Você configurou um AppWidget usando o Glance.

Usar a classe AppWidgetProvider para processar transmissões de widgets

O widget de coordenadas GlanceAppWidgetReceiver transmite e atualiza o estado da plataforma ao estender o AppWidgetProvider subjacente. Ele recebe eventos da plataforma quando o widget é atualizado, excluído, ativado ou desativado, traduzindo-os em solicitações do ciclo de vida do Compose.

Declarar um widget no manifesto

Declare a subclasse da classe GlanceAppWidgetReceiver como um broadcast receiver no arquivo AndroidManifest.xml:

<receiver android:name="ExampleAppWidgetReceiver"
          android:exported="false">
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>
    <meta-data android:name="android.appwidget.provider"
               android:resource="@xml/my_app_widget_info" />
</receiver>

O elemento <receiver> requer o atributo android:name, que especifica a classe de receptor. O receptor precisa aceitar a ação de transmissão ACTION_APPWIDGET_UPDATE dentro do <intent-filter>.

O elemento <meta-data> precisa identificar o nome como android.appwidget.provider, e o atributo android:resource precisa apontar para o recurso de metadados XML AppWidgetProviderInfo (@xml/my_app_widget_info).

Implementar a classe AppWidgetProvider

No Glance, você estende GlanceAppWidgetReceiver em vez de AppWidgetProvider diretamente. Implemente-o vinculando o receptor à sua instância do GlanceAppWidget. Os callbacks principais disponíveis em GlanceAppWidgetReceiver funcionam da seguinte maneira:

  • onUpdate(): substituído automaticamente pelo Glance para executar atualizações de composição. Se você substituir onUpdate manualmente, será necessário chamar super.onUpdate para permitir que o Glance inicie as linhas de execução de composição com êxito.
  • onAppWidgetOptionsChanged(): chamado quando o widget é posicionado ou redimensionado pela primeira vez. As opções de leitura rápida agrupam itens nos bastidores para que seu layout se ajuste sem problemas com base nas dimensões de execução.
  • onDeleted(Context, IntArray): invocado sempre que uma instância específica de widget é excluída pelo usuário.
  • onEnabled(Context): acionado quando a primeira instância do widget é criada. Excelente para executar migrações globais.
  • onDisabled(Context): chamado quando a última instância ativa do provedor é removida.
  • onReceive(Context, Intent): intercepta todas as transmissões da plataforma antes de métodos de callback específicos. Você precisa garantir que qualquer lógica de receptor personalizada que você escreva chame super.onReceive(context, intent) e nunca chame goAsync por conta própria, já que o Glance encaminha o trabalho de forma assíncrona.

Receber intents de transmissão de widgets

Em segundo plano, o GlanceAppWidgetReceiver filtra e processa as seguintes intenções de transmissão de widgets da plataforma fundamentais:

Criar interface

O snippet a seguir demonstra como criar a interface:

/* Import Glance Composables
 In the event there is a name clash with the Compose classes of the same name,
 you may rename the imports per https://kotlinlang.org/docs/packages.html#imports
 using the `as` keyword.

import androidx.glance.Button
import androidx.glance.layout.Column
import androidx.glance.layout.Row
import androidx.glance.text.Text
*/
class MyAppWidget : GlanceAppWidget() {

    override suspend fun provideGlance(context: Context, id: GlanceId) {
        // Load data needed to render the AppWidget.
        // Use `withContext` to switch to another thread for long running
        // operations.

        provideContent {
            // create your AppWidget here
            MyContent()
        }
    }

    @Composable
    private fun MyContent() {
        Column(
            modifier = GlanceModifier.fillMaxSize(),
            verticalAlignment = Alignment.Top,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Text(text = "Where to?", modifier = GlanceModifier.padding(12.dp))
            Row(horizontalAlignment = Alignment.CenterHorizontally) {
                Button(
                    text = "Home",
                    onClick = actionStartActivity<MyActivity>()
                )
                Button(
                    text = "Work",
                    onClick = actionStartActivity<MyActivity>()
                )
            }
        }
    }
}

O exemplo de código anterior faz o seguinte:

  • No Column de nível superior, os itens são colocados verticalmente um após o outro.
  • O Column aumenta o tamanho para corresponder ao espaço disponível (via GlanceModifier) e alinha o conteúdo à parte de cima (verticalAlignment) e o centraliza horizontalmente (horizontalAlignment).
  • O conteúdo do Column é definido usando a lambda. A ordem é importante.
    • O primeiro item no Column é um componente Text com 12.dp de padding.
    • O segundo item é um Row, em que os itens são colocados horizontalmente um após o outro, com dois Buttons centralizados horizontalmente (horizontalAlignment). A exibição final depende do espaço disponível. Confira um exemplo de como ela pode aparecer:
destination_widget
Figura 1. Um exemplo de interface.

É possível mudar os valores de alinhamento ou aplicar valores de modificadores diferentes (como padding) para mudar o posicionamento e o tamanho dos componentes. Consulte a documentação de referência para ver uma lista completa de componentes, parâmetros e modificadores disponíveis para cada classe.

Implementar cantos arredondados

O Android 12 introduz parâmetros de sistema para personalizar os raios dos cantos dos widgets de apps de forma dinâmica:

  • system_app_widget_background_radius: especifica o raio do canto do contêiner de plano de fundo do widget (nunca maior que 28 dp).
  • Raio interno:para evitar o corte do conteúdo, calcule um raio proporcional para o conteúdo interno com base no contorno do plano de fundo do sistema: systemRadiusValue - widgetPadding

No Glance, é possível aplicar propriedades de dimensionamento de raio do canto de forma dinâmica na composição usando GlanceModifier.cornerRadius(android.R.dimen.system_app_widget_background_radius).

Para compatibilidade com versões anteriores em dispositivos com o Android 11 (nível 30 da API) ou versões anteriores, implemente atributos personalizados e substituições de recursos de tema personalizados:

  • /values/attrs.xml

    <resources>
    <attr name="backgroundRadius" format="dimension" />
    </resources>
    
  • /values/styles.xml

    <resources>
    <style name="MyWidgetTheme">
      <item name="backgroundRadius">@dimen/my_background_radius_dimen</item>
    </style>
    </resources>
    
  • /values-31/styles.xml

    <resources>
    <style name="MyWidgetTheme" parent="@android:style/Theme.DeviceDefault.DayNight">
      <item name="backgroundRadius">@android:dimen/system_app_widget_background_radius</item>
    </style>
    </resources>
    
  • /drawable/my_widget_background.xml

    <shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <corners android:radius="?attr/backgroundRadius" />
    </shape>