Criar um widget de app com o Glance

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 do Glance é que não há um XML initialLayout, mas é necessário 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 widget. Defina o AppWidgetProviderInfo no arquivo de recursos 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 sejam múltiplos inteiros das células da grade, por exemplo, duas células horizontalmente por três células verticalmente.

Os atributos de dimensionamento de widgets permitem especificar um tamanho padrão para o widget e fornecer limites inferiores e superiores no 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 targetCellWidth e targetCellHeight atributos especificam o tamanho padrão do widget em termos de células da grade. Esses atributos são ignorados no Android 11 e versões anteriores e podem ser ignorados se a tela inicial não oferecer suporte a 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 as dimensões das células, os valores serão arredondados para cima para 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 poderá usar minWidth e minHeight se o dispositivo do usuário não oferecer suporte a targetCellWidth e targetCellHeight. Se houver suporte, os targetCellWidth e targetCellHeight atributos terão precedência sobre os minWidth e minHeight atributos.
minResizeWidth e minResizeHeight Especifica o tamanho mínimo absoluto do widget. Esses valores especificam o tamanho abaixo do qual o widget fica ilegível ou inutilizável. O uso desses atributos permite que o usuário redimensione o widget para um tamanho menor que o tamanho 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 minResizeHeight atributo é ignorado se for maior que minHeight ou se o redimensionamento vertical não estiver ativado.
maxResizeWidth e maxResizeHeight Especifica o tamanho máximo recomendado do widget. Se os valores não forem um múltiplo das dimensões da célula da grade, eles serão arredondados para cima para 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 é 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. Você pode usar esse atributo para permitir o redimensionamento dos widgets da tela inicial: na horizontal, vertical, ou nos dois eixos. Os usuários tocam em um widget e o mantêm pressionado para mostrar as alças de redimensionamento, em seguida, arrastam as alças horizontais ou verticais para mudar o tamanho na grade do layout. Os valores do atributo resizeMode incluem horizontal, vertical e none. Para declarar um widget como redimensionável horizontal e verticalmente, 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 do widget é 2x2 por padrão. O widget pode ser redimensionado para 2x1 ou 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 do widget é 3x2 por padrão. O widget pode ser redimensionado para 2x1 ou até a tela cheia.

Outros atributos de widget

A tabela a seguir descreve os atributos <appwidget-provider> relacionados a qualidades diferentes do dimensionamento de widgets.

Atributos e descrição
updatePeriodMillis Define a frequência com que o framework do 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 economizar 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 o guia Implementar a atividade de configuração.
description Especifica a descrição que o seletor de widgets vai mostrar para o widget. Introduzido no Android 12.
previewLayout (Android 12) e previewImage (Android 11 e versões anteriores)
  • A partir do Android 12, o previewLayout atributo 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 de design.
  • No Android 11 ou versões anteriores, o previewImage atributo especifica uma captura de tela de imagem estática do widget, que aparece no seletor de widgets.
Recomendamos especificar os dois para que o app tenha um fallback adequado em plataformas mais antigas. Para plataformas mais recentes (Android 15 e mais recentes), é possível definir visualizações geradas ao vivo no Kotlin usando `GlanceAppWidget.providePreview`. Consulte o guia Visualizações geradas.
autoAdvanceViewId Especifica o ID de visualização da subexibição do widget, que é avançado 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. No Android 5.0 e versões mais recentes, apenas home_screen é válido.
widgetFeatures Declara os recursos com suporte do widget. Por exemplo, se a configuração do widget for opcional, especifique configuration_optional e reconfigurable.

Definir GlanceAppWidget

  1. Crie uma nova classe que se estenda de GlanceAppWidget e substitua o método provideGlance. Esse é o método em que você pode carregar os dados necessários para renderizar o 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 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 GlanceAppWidgetReceiver para processar transmissões de widgets

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

Declarar um widget no manifesto

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

<receiver android:name="MyReceiver"
          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 do receptor. O receptor precisa aceitar a ACTION_APPWIDGET_UPDATE ação de transmissão 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 GlanceAppWidgetReceiver

No Glance, você estende GlanceAppWidgetReceiver em vez de AppWidgetProvider diretamente. Implemente-o vinculando o receptor à instância GlanceAppWidget. Os callbacks principais disponíveis no 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 threads de composição.
  • onAppWidgetOptionsChanged(): chamado quando o widget é colocado ou redimensionado pela primeira vez. O Glance lê itens de pacote de opções em segundo plano para que o layout seja ajustado perfeitamente com base nas dimensões de execução.
  • onDeleted(Context, IntArray): invocado sempre que uma instância de widget específica é 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. É necessário garantir que qualquer lógica de receptor personalizada que você escreva chame super.onReceive(context, intent) e nunca chame goAsync , já que o Glance encaminha o trabalho automaticamente de forma assíncrona.

Receber intents de transmissão de widgets

Em segundo plano, GlanceAppWidgetReceiver filtra e processa os seguintes intents de transmissão de widgets da plataforma:

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 expande o tamanho para corresponder ao espaço disponível (pelo GlanceModifier e alinha o conteúdo à parte de cima (verticalAlignment) e o centraliza horizontalmente (horizontalAlignment).
  • O conteúdo da Column é definido usando a lambda. A ordem é importante.
    • O primeiro item na Column é um componente Text com 12.dp de preenchimento.
    • O segundo item é uma 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. A imagem a seguir é um exemplo de como ela pode ser:
destination_widget
Figura 1. Um exemplo de interface.

É possível mudar os valores de alinhamento ou aplicar valores de modificador diferentes (como preenchimento) para mudar o posicionamento e o tamanho dos componentes. Consulte a documentação de referência para conferir 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 app 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 recorte de 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 de 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 fallbacks 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>