Criar um widget simples

Os widgets de apps são visualizações em miniatura que você pode incorporar a outros apps (como a tela inicial) e receber atualizações periódicas. Essas visualizações são chamadas de widgets na interface do usuário, e é possível publicá-las com um provedor de widgets de apps (ou provedor de widgets). Um componente de app que contém outros widgets é chamado de host de widgets de apps (ou host de widgets). A Figura 1 mostra um exemplo de widget de música:

Exemplo de widget de música
Figura 1. Exemplo de um widget de música.

Este documento descreve como publicar um widget usando um provedor. Para saber mais sobre como criar seu próprio AppWidgetHost para hospedar widgets de apps, consulte Criar um host de widgets.

Para aprender a criar seu widget, consulte Visão geral dos widgets de app.

Componentes do widget

Para criar um widget, você precisa dos seguintes componentes básicos:

Objeto AppWidgetProviderInfo
Descreve os metadados de um widget, como o layout, a frequência de atualização e a classe AppWidgetProvider. AppWidgetProviderInfo é definido em XML, conforme descrito neste documento.
Classe AppWidgetProvider
Define os métodos básicos que permitem interagir programaticamente com o widget. Por ela, você recebe transmissões quando o widget é atualizado, ativado, desativado ou excluído. Declare AppWidgetProvider no manifesto e depois implemente-o, conforme descrito neste documento.
Layout de visualização
Define o layout inicial do widget. O layout é definido em XML, conforme descrito neste documento.

A Figura 2 mostra como esses componentes se encaixam no fluxo geral de processamento de widgets de apps.

Fluxo de processamento de widgets de apps
Figura 2. Fluxo de processamento de widgets de apps.

Caso o widget precise de configuração do usuário, implemente a atividade de configuração do app. Essa atividade permite que os usuários modifiquem as configurações do widget, por exemplo, o fuso horário de um widget de relógio.

Também recomendamos as seguintes melhorias: layouts flexíveis de widget, aprimoramentos diversos, widgets avançados, widgets de coleção e criação de um host de widgets.

Declarar o XML AppWidgetProviderInfo

O objeto AppWidgetProviderInfo define as qualidades essenciais de um widget. Defina o objeto AppWidgetProviderInfo em um arquivo de recurso XML usando um único elemento <appwidget-provider> e salve-o na pasta res/xml/ do projeto.

Isso é mostrado neste exemplo:

<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/example_loading_appwidget"
    android:configure="com.example.android.ExampleAppWidgetConfigurationActivity"
    android:resizeMode="horizontal|vertical"
    android:widgetCategory="home_screen"
    android:widgetFeatures="reconfigurable|configuration_optional">
</appwidget-provider>

Atributos de dimensionamento do widget

A tela inicial padrão posiciona os 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 células na vertical.

Com os atributos de dimensionamento do widget, você pode especificar um tamanho padrão e fornecer limites inferior e superior para o tamanho dele. Nesse contexto, o tamanho padrão de um widget é aquele que ele assume quando é adicionado à tela inicial pela primeira vez.

A tabela abaixo descreve os atributos <appwidget-provider> relacionados ao dimensionamento do widget:

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 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 altura mínimas de um widget não corresponderem às dimensões das células, eles serão arredondados para o tamanho de célula mais próximo.
Recomendamos especificar os dois conjuntos de atributos (targetCellWidth e targetCellHeight, além de minWidth e minHeight) para que seu app possa voltar a usar minWidth e minHeight se o dispositivo do usuário não oferecer suporte a 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 em que o widget está 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 do widget. O atributo minResizeWidth será ignorado se for maior que minWidth ou se o redimensionamento horizontal não estiver ativado. Consulte resizeMode. Da mesma forma, o atributo minResizeHeight será 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 das células da grade, eles serão arredondados para o tamanho de célula mais próximo. O atributo maxResizeWidth será 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 maior 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 tornar os widgets da tela inicial redimensionáveis na horizontal, na vertical ou nos dois eixos. Os usuários tocam e pressionam um widget para mostrar as alças de redimensionamento, depois arrastam as alças horizontais ou verticais para mudar o tamanho dele na grade de layout. Os valores do 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 da tabela anterior afetam o dimensionamento do widget, suponha as seguintes especificações:

  • Uma célula de grade tem 30 dp de largura e 50 dp de altura.
  • A especificação de atributo abaixo é 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 é 2 x 2 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 é 3 x 2 por padrão. O widget pode ser redimensionado para 2 x 1 ou para tela cheia.

Outros atributos do widget

A tabela abaixo descreve os atributos <appwidget-provider> relacionados a qualidades diferentes do dimensionamento do widget.

Atributos e descrição
updatePeriodMillis Define com que frequência o framework do widget solicita uma atualização do AppWidgetProvider chamando o método de callback onUpdate(). Não há garantia de que a atualização real ocorra exatamente no horário com esse valor. Recomendamos atualizar com a menor frequência possível, no máximo uma vez por hora, para economizar a bateria. Para ver a lista completa de considerações para escolher um período de atualização apropriado, consulte Otimizações para atualizar o conteúdo do widget.
initialLayout Aponta para o recurso de layout que define o layout do widget.
configure Define a atividade que é iniciada quando o usuário adiciona o widget, permitindo que ele configure as propriedades dele. Consulte Permitir que usuários configurem widgets. A partir do Android 12, o app pode pular a configuração inicial. Consulte Usar a configuração padrão do widget para ver mais detalhes.
description Especifica a descrição para o seletor exibir 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 como o tamanho padrão do widget. O ideal é que o XML de layout especificado como esse atributo seja o mesmo XML de layout que o widget real, com valores padrão realistas.
  • No Android 11 ou versões anteriores, o atributo previewImage especifica uma visualização da aparência do widget após a configuração, que será vista pelo usuário ao selecionar o widget de app. Se ele não for fornecido, o usuário verá o ícone do app na tela de início. Esse campo corresponde ao atributo android:previewImage no elemento <receiver> no arquivo AndroidManifest.xml.
Observação:recomendamos especificar os atributos previewImage e previewLayout para que seu app possa voltar a usar previewImage se o dispositivo do usuário não oferecer suporte a previewLayout. Para mais detalhes, consulte Compatibilidade com versões anteriores com visualizações de widgets escalonáveis.
autoAdvanceViewId Especifica o ID da visualização da subvisualizaçã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 em ambas. Para o Android 5.0 e mais recentes, apenas home_screen é válido.
widgetFeatures Declara os recursos compatíveis com o widget. Por exemplo, se você quiser que o widget use a configuração padrão quando um usuário adicioná-lo, especifique as sinalizações configuration_optional e reconfigurable. Isso vai ignorar a inicialização da atividade de configuração depois que o usuário adicionar o widget. O usuário ainda poderá reconfigurar o widget depois.

Usar a classe AppWidgetProvider para processar transmissões de widget.

A classe AppWidgetProvider processa transmissões de widget e o atualiza em resposta a eventos de ciclo de vida. As seções abaixo descrevem como declarar AppWidgetProvider no manifesto e implementá-lo.

Declarar um widget no manifesto

Primeiro, declare a classe AppWidgetProvider no arquivo AndroidManifest.xml do app, conforme mostrado no exemplo a seguir.

<receiver android:name="ExampleAppWidgetProvider"
                 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/example_appwidget_info" />
</receiver>

O elemento <receiver> requer o atributo android:name, que especifica o AppWidgetProvider usado pelo widget. O componente não pode ser exportado, a menos que um processo separado precise transmitir ao AppWidgetProvider, o que geralmente não é o caso.

O elemento <intent-filter> precisa incluir um <action> com o atributo android:name. Esse atributo especifica que o AppWidgetProvider aceita a transmissão ACTION_APPWIDGET_UPDATE. Essa é a única transmissão que precisa ser declarada explicitamente. O AppWidgetManager envia automaticamente todas as outras transmissões de widget para o AppWidgetProvider, conforme necessário.

O elemento <meta-data> especifica o recurso AppWidgetProviderInfo e requer os seguintes atributos:

  • android:name: especifica o nome dos metadados. Use android.appwidget.provider para identificar os dados como o descritor AppWidgetProviderInfo.
  • android:resource: especifica o local do recurso AppWidgetProviderInfo.

Implementar a classe AppWidgetProvider

A classe AppWidgetProvider estende BroadcastReceiver como uma classe de conveniência para processar transmissões de widget. Ele recebe apenas as transmissões de eventos relevantes para o widget, como quando o widget é atualizado, excluído, ativado e desativado. Quando esses eventos de transmissão ocorrem, os métodos AppWidgetProvider abaixo são chamados:

onUpdate()
É chamado para atualizar o widget em intervalos definidos pelo atributo updatePeriodMillis no AppWidgetProviderInfo. Consulte a tabela que descreve outros atributos de widget nesta página para mais informações.
Esse método também é chamado quando o usuário adiciona o widget. Ele executa a configuração essencial, como definir manipuladores de eventos para objetos View ou iniciar jobs para carregar dados a serem mostrados no widget. No entanto, se você declarar uma atividade de configuração sem a flag configuration_optional, esse método não será chamado quando o usuário adicionar o widget, mas será chamado para as atualizações subsequentes. É responsabilidade da atividade de configuração realizar a primeira atualização quando a configuração for concluída. Consulte Permitir que os usuários configurem widgets de apps para mais informações.
O callback mais importante é onUpdate(). Consulte Gerenciar eventos com a classe onUpdate() nesta página para mais informações.
onAppWidgetOptionsChanged()

Isso é chamado quando o widget é colocado pela primeira vez e sempre que ele é redimensionado. Use esse callback para mostrar ou ocultar conteúdo com base nos intervalos de tamanho do widget. Confira as faixas de tamanho e, a partir do Android 12, a lista de tamanhos possíveis que uma instância de widget pode ter, chamando getAppWidgetOptions(), que retorna uma Bundle que inclui o seguinte:

onDeleted(Context, int[])

Isso é chamado sempre que um widget é excluído do host de widgets.

onEnabled(Context)

Isso é chamado quando uma instância do widget é criada pela primeira vez. Por exemplo, se o usuário adicionar duas instâncias do seu widget, isso será chamado apenas na primeira vez. Se você precisar abrir um novo banco de dados ou executar outra configuração que só precise ocorrer uma vez para todas as instâncias de widget, este é um bom lugar para fazer isso.

onDisabled(Context)

Isso é chamado quando a última instância do seu widget é excluída do host de widgets. É aqui que você limpa qualquer trabalho feito no onEnabled(Context), como a exclusão de um banco de dados temporário.

onReceive(Context, Intent)

Ele é chamado para cada transmissão e antes de cada um dos métodos de callback anteriores. Normalmente, não é necessário implementar esse método, porque a implementação AppWidgetProvider padrão filtra todas as transmissões do widget e chama os métodos anteriores conforme apropriado.

Você precisa declarar a implementação da classe AppWidgetProvider como um broadcast receiver usando o elemento <receiver> no AndroidManifest. Consulte Declarar um widget no manifesto nesta página para saber mais.

Processar eventos com a classe onUpdate()

O callback AppWidgetProvider mais importante é onUpdate(), porque ele é chamado quando cada widget é adicionado a um host, a menos que você use uma atividade de configuração sem a flag configuration_optional. Se o widget aceitar qualquer evento de interação do usuário, registre os manipuladores de eventos nesse callback. Se o widget não criar arquivos ou bancos de dados temporários nem executar outro trabalho que exija limpeza, onUpdate() poderá ser o único método de callback que você precisa definir.

Por exemplo, se você quiser um widget com um botão que inicie uma atividade ao tocar, use a seguinte implementação de AppWidgetProvider:

Kotlin

class ExampleAppWidgetProvider : AppWidgetProvider() {

    override fun onUpdate(
            context: Context,
            appWidgetManager: AppWidgetManager,
            appWidgetIds: IntArray
    ) {
        // Perform this loop procedure for each widget that belongs to this
        // provider.
        appWidgetIds.forEach { appWidgetId ->
            // Create an Intent to launch ExampleActivity.
            val pendingIntent: PendingIntent = PendingIntent.getActivity(
                    /* context = */ context,
                    /* requestCode = */  0,
                    /* intent = */ Intent(context, ExampleActivity::class.java),
                    /* flags = */ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
            )

            // Get the layout for the widget and attach an onClick listener to
            // the button.
            val views: RemoteViews = RemoteViews(
                    context.packageName,
                    R.layout.appwidget_provider_layout
            ).apply {
                setOnClickPendingIntent(R.id.button, pendingIntent)
            }

            // Tell the AppWidgetManager to perform an update on the current
            // widget.
            appWidgetManager.updateAppWidget(appWidgetId, views)
        }
    }
}

Java

public class ExampleAppWidgetProvider extends AppWidgetProvider {

    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // Perform this loop procedure for each widget that belongs to this
        // provider.
        for (int i=0; i < appWidgetIds.length; i++) {
            int appWidgetId = appWidgetIds[i];
            // Create an Intent to launch ExampleActivity
            Intent intent = new Intent(context, ExampleActivity.class);
            PendingIntent pendingIntent = PendingIntent.getActivity(
                /* context = */ context,
                /* requestCode = */ 0,
                /* intent = */ intent,
                /* flags = */ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
            );

            // Get the layout for the widget and attach an onClick listener to
            // the button.
            RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.example_appwidget_layout);
            views.setOnClickPendingIntent(R.id.button, pendingIntent);

            // Tell the AppWidgetManager to perform an update on the current app
            // widget.
            appWidgetManager.updateAppWidget(appWidgetId, views);
        }
    }
}

Esse AppWidgetProvider define apenas o método onUpdate(), usando-o para criar uma PendingIntent que inicia um Activity e o anexa ao botão do widget usando setOnClickPendingIntent(int, PendingIntent). Ela inclui uma repetição que itera em cada entrada em appWidgetIds, que é uma matriz de IDs que identificam cada widget criado por esse provedor. Se o usuário criar mais de uma instância do widget, todas elas serão atualizadas simultaneamente. No entanto, apenas uma programação updatePeriodMillis é gerenciada para todas as instâncias do widget. Por exemplo, se a programação de atualização for definida para ser a cada duas horas e uma segunda instância do widget for adicionada uma hora após a primeira, ambas serão atualizadas no período definido pela primeira, e o segundo período de atualização será ignorado. Eles são atualizados a cada duas horas, não a cada hora.

Consulte a classe de exemplo ExampleAppWidgetProvider.java para mais detalhes.

Receber intents de transmissão de widget

AppWidgetProvider é uma classe de conveniência. Se você quiser receber as transmissões diretamente do widget, implemente seu próprio BroadcastReceiver ou substitua o callback onReceive(Context,Intent). As intents com as quais você precisa se preocupar são as seguintes:

Criar o layout do widget

Você precisa definir um layout inicial para o widget em XML e salvá-lo no diretório res/layout/ do projeto. Consulte os detalhes em Diretrizes de design.

A criação do layout do widget será simples se você conhecer os layouts. No entanto, esteja ciente de que os layouts de widget são baseados em RemoteViews, que não oferece suporte a todos os tipos de layout ou widget de visualização. Não é possível usar visualizações personalizadas ou subclasses das visualizações compatíveis com RemoteViews.

O RemoteViews também oferece suporte a ViewStub, que é um View invisível e de tamanho zero que pode ser usado para inflar lentamente os recursos de layout no momento da execução.

Suporte para comportamento com estado

O Android 12 adiciona suporte a comportamentos com estado usando os componentes já existentes a seguir:

O widget ainda não tem estado. O app precisa armazenar o estado e se registrar para eventos de mudança de estado.

Exemplo de widget de lista de compras mostrando comportamento com estado
Figura 3. Exemplo de comportamento com estado.

O exemplo de código a seguir mostra como implementar esses componentes.

Kotlin

// Check the view.
remoteView.setCompoundButtonChecked(R.id.my_checkbox, true)

// Check a radio group.
remoteView.setRadioGroupChecked(R.id.my_radio_group, R.id.radio_button_2)

// Listen for check changes. The intent has an extra with the key
// EXTRA_CHECKED that specifies the current checked state of the view.
remoteView.setOnCheckedChangeResponse(
        R.id.my_checkbox,
        RemoteViews.RemoteResponse.fromPendingIntent(onCheckedChangePendingIntent)
)

Java

// Check the view.
remoteView.setCompoundButtonChecked(R.id.my_checkbox, true);

// Check a radio group.
remoteView.setRadioGroupChecked(R.id.my_radio_group, R.id.radio_button_2);

// Listen for check changes. The intent has an extra with the key
// EXTRA_CHECKED that specifies the current checked state of the view.
remoteView.setOnCheckedChangeResponse(
    R.id.my_checkbox,
    RemoteViews.RemoteResponse.fromPendingIntent(onCheckedChangePendingIntent));

Forneça dois layouts: um destinado a dispositivos com o Android 12 ou versões mais recentes na res/layout-v31 e o outro direcionado ao Android 11 anterior ou anterior na pasta res/layout padrão.

Implementar cantos arredondados

O Android 12 introduz os parâmetros de sistema abaixo para definir os radios dos cantos arredondados do widget:

O exemplo a seguir mostra um widget que usa system_app_widget_background_radius para o canto e system_app_widget_inner_radius para as visualizações dentro do widget.

Widget mostrando raios do plano de fundo e visualizações dentro do widget
Figura 4. Cantos arredondados.

1 Canto do widget.

2 Canto de uma visualização dentro do widget.

Considerações importantes para cantos arredondados

  • Telas de início e fabricantes de dispositivos terceirizados podem substituir o parâmetro system_app_widget_background_radius para que ele seja menor que 28 dp. O parâmetro system_app_widget_inner_radius é sempre 8 dp menor que o valor de system_app_widget_background_radius.
  • Se o widget não usar @android:id/background ou definir um plano de fundo que recorte o conteúdo com base no contorno, com android:clipToOutline definido como true, a tela de início vai identificar automaticamente o plano de fundo e recortar o widget usando um retângulo com cantos arredondados de até 16 dp. Consulte Verificar se o widget é compatível com o Android 12.

Para compatibilidade de widgets com versões anteriores do Android, recomendamos definir atributos personalizados e usar um tema personalizado para substituí-los no Android 12, conforme mostrado nestes arquivos XML de exemplo:

/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>

/layout/my_widget_layout.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  ...
  android:background="@drawable/my_widget_background" />