Oferecer layouts de widget flexíveis

Experimente trabalhar com o Compose
O Jetpack Compose é o kit de ferramentas de interface recomendado para o Android. Aprenda a criar widgets usando APIs no estilo do Compose.

Esta página descreve os refinamentos para o dimensionamento de widgets e a maior flexibilidade introduzida no Android 12 (nível 31 da API). Ela também detalha como determinar um tamanho para o widget.

Usar APIs melhoradas para tamanhos e layouts de widgets

A partir do Android 12 (nível 31 da API), você pode fornecer atributos de tamanho mais refinados e layouts flexíveis fazendo o seguinte, conforme descrito nas seções a seguir:

  1. Especifique outras restrições de dimensionamento de widgets.

  2. Forneça layouts responsivos ou layouts exatos.

Em versões anteriores do Android, é possível acessar os intervalos de tamanho de um widget usando os OPTION_APPWIDGET_MIN_WIDTH, OPTION_APPWIDGET_MIN_HEIGHT, OPTION_APPWIDGET_MAX_WIDTH, e OPTION_APPWIDGET_MAX_HEIGHT extras e estimar o tamanho do widget, mas essa lógica não funciona em todas as situações. Para widgets destinados ao Android 12 ou versões mais recentes, recomendamos fornecer layoutsresponsivos ou exatos.

Especifique outras restrições de dimensionamento de widgets

O Android 12 adiciona APIs que permitem garantir que o widget seja dimensionado de forma mais confiável em diferentes dispositivos com diferentes tamanhos de tela.

Além dos atributos minWidth, minHeight, minResizeWidth, e minResizeHeight, use os novos atributos appwidget-provider a seguir:

  • targetCellWidth e targetCellHeight: definem o tamanho de destino do widget em termos de células de grade na tela de início. Se definidos, esses atributos serão usados em vez de minWidth ou minHeight.

  • maxResizeWidth e maxResizeHeight: definem o tamanho máximo permitido pela tela de início ao usuário para redimensionar o widget.

O XML a seguir mostra como usar os atributos de dimensionamento.

<appwidget-provider
  ...
  android:targetCellWidth="3"
  android:targetCellHeight="2"
  android:maxResizeWidth="250dp"
  android:maxResizeHeight="110dp">
</appwidget-provider>

Fornecer layouts responsivos

Se o layout precisar mudar dependendo do tamanho do widget, recomendamos criar um pequeno conjunto de layouts, cada um válido para uma série de tamanhos. Se isso não for possível, outra opção é fornecer layouts com base no tamanho exato do widget no momento da execução, conforme descrito nesta página.

Esse recurso permite um dimensionamento mais suave e uma melhor integridade do sistema em geral, porque o sistema não precisa ativar o app sempre que ele exibir o widget em um tamanho diferente.

O exemplo de código a seguir mostra como fornecer uma lista de layouts.

Kotlin

override fun onUpdate(...) {
    val smallView = ...
    val tallView = ...
    val wideView = ...

    val viewMapping: Map<SizeF, RemoteViews> = mapOf(
            SizeF(150f, 100f) to smallView,
            SizeF(150f, 200f) to tallView,
            SizeF(215f, 100f) to wideView
    )
    val remoteViews = RemoteViews(viewMapping)

    appWidgetManager.updateAppWidget(id, remoteViews)
}

Java

@Override
public void onUpdate(...) {
    RemoteViews smallView = ...;
    RemoteViews tallView = ...;
    RemoteViews wideView = ...;

    Map<SizeF, RemoteViews> viewMapping = new ArrayMap<>();
    viewMapping.put(new SizeF(150f, 100f), smallView);
    viewMapping.put(new SizeF(150f, 200f), tallView);
    viewMapping.put(new SizeF(215f, 100f), wideView);
    RemoteViews remoteViews = new RemoteViews(viewMapping);

    appWidgetManager.updateAppWidget(id, remoteViews);
}

Suponha que o widget tenha os seguintes atributos:

<appwidget-provider
    android:minResizeWidth="160dp"
    android:minResizeHeight="110dp"
    android:maxResizeWidth="250dp"
    android:maxResizeHeight="200dp">
</appwidget-provider>

O snippet de código anterior significa o seguinte:

  • smallView oferece suporte de 160 dp (minResizeWidth) × 110 dp (minResizeHeight) a 160 dp × 199 dp (próximo ponto de corte - 1 dp).
  • tallView oferece suporte de 160 dp × 200 dp a 214 dp (próximo ponto de corte - 1) × 200 dp.
  • wideView oferece suporte de 215 dp × 110 dp (minResizeHeight) a 250 dp (maxResizeWidth) × 200 dp (maxResizeHeight).

O widget precisa oferecer suporte ao intervalo de tamanho de minResizeWidth × minResizeHeight a maxResizeWidth × maxResizeHeight. Dentro desse intervalo, você pode decidir o ponto de corte para mudar os layouts.

Exemplo de layout responsivo
Figura 1. Exemplo de um layout responsivo.

Fornecer layouts exatos

Se não for viável usar um pequeno conjunto de layouts responsivos, é possível fornecer layouts diferentes de acordo com os tamanhos de exibição do widget. Normalmente, há dois tamanhos para smartphones (modo retrato e paisagem) e quatro para dispositivos dobráveis.

Para implementar essa solução, o app precisa realizar as seguintes etapas:

  1. Sobrecarregue AppWidgetProvider.onAppWidgetOptionsChanged(), que é chamado quando o conjunto de tamanhos muda.

  2. Chame AppWidgetManager.getAppWidgetOptions(), que retorna um Bundle contendo os tamanhos.

  3. Acesse a chave AppWidgetManager.OPTION_APPWIDGET_SIZES do Bundle.

O exemplo de código a seguir mostra como fornecer layouts exatos.

Kotlin

override fun onAppWidgetOptionsChanged(
        context: Context,
        appWidgetManager: AppWidgetManager,
        id: Int,
        newOptions: Bundle?
) {
    super.onAppWidgetOptionsChanged(context, appWidgetManager, id, newOptions)
    // Get the new sizes.
    val sizes = newOptions?.getParcelableArrayList<SizeF>(
            AppWidgetManager.OPTION_APPWIDGET_SIZES
    )
    // Check that the list of sizes is provided by the launcher.
    if (sizes.isNullOrEmpty()) {
        return
    }
    // Map the sizes to the RemoteViews that you want.
    val remoteViews = RemoteViews(sizes.associateWith(::createRemoteViews))
    appWidgetManager.updateAppWidget(id, remoteViews)
}

// Create the RemoteViews for the given size.
private fun createRemoteViews(size: SizeF): RemoteViews { }

Java

@Override
public void onAppWidgetOptionsChanged(
    Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) {
    super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
    // Get the new sizes.
    ArrayList<SizeF> sizes =
        newOptions.getParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES);
    // Check that the list of sizes is provided by the launcher.
    if (sizes == null || sizes.isEmpty()) {
      return;
    }
    // Map the sizes to the RemoteViews that you want.
    Map<SizeF, RemoteViews> viewMapping = new ArrayMap<>();
    for (SizeF size : sizes) {
        viewMapping.put(size, createRemoteViews(size));
    }
    RemoteViews remoteViews = new RemoteViews(viewMapping);
    appWidgetManager.updateAppWidget(id, remoteViews);
}

// Create the RemoteViews for the given size.
private RemoteViews createRemoteViews(SizeF size) { }

Determinar um tamanho para o widget

Cada widget precisa definir um targetCellWidth e targetCellHeight para dispositivos com o Android 12 ou versões mais recentes ou minWidth e minHeight para todas as versões do Android, indicando a quantidade mínima de espaço que ele consome por padrão. No entanto, quando os usuários adicionam um widget à tela inicial, ele geralmente ocupa mais que a largura e a altura mínimas especificadas.

As telas iniciais do Android oferecem aos usuários uma grade de espaços disponíveis em que eles podem colocar widgets e ícones. Essa grade pode variar de acordo com o dispositivo. Por exemplo, muitos smartphones oferecem uma grade de 5x4, e os tablets podem oferecer uma grade maior. Quando seu widget é adicionado, ele é esticado para ocupar o número mínimo de células, horizontal e verticalmente, necessário para satisfazer as restrições de targetCellWidth e targetCellHeight em dispositivos com o Android 12 ou versões mais recentes ou as restrições minWidth e minHeight em dispositivos com o Android 11 (nível da API 30) ou versões anteriores.

A largura e a altura de uma célula e o tamanho das margens automáticas aplicadas aos widgets podem variar entre os dispositivos. Use a tabela a seguir para fazer uma estimativa aproximada das dimensões mínimas do widget em um smartphone com grade de 5x4, de acordo com o número de células de grade ocupadas que você quer:

Número de células (largura x altura) Tamanho disponível no modo retrato (dp) Tamanho disponível no modo paisagem (dp)
1x1 57 x 102 dp 127 x 51 dp
2x1 130 x 102 dp 269 x 51 dp
3x1 203 x 102 dp 412 x 51 dp
4x1 276 x 102 dp 554 x 51 dp
5x1 349 x 102 dp 697 x 51 dp
5x2 349 x 220 dp 697 x 117 dp
5x3 349 x 337 dp 697 x 184 dp
5x4 349 x 455 dp 697 x 250 dp
... ...
n x m (73n - 16) x (118m - 16) (142n - 15) x (66m - 15)

Use os tamanhos de célula do modo retrato para informar os valores fornecidos para os atributos minWidth, minResizeWidth e maxResizeWidth. Da mesma forma, use os tamanhos de célula do modo paisagem para informar os valores fornecidos para os atributos minHeight, minResizeHeight e maxResizeHeight.

O motivo é que a largura da célula é normalmente menor no modo retrato do que no modo paisagem e, da mesma forma, a altura da célula é normalmente menor no modo paisagem do que no modo retrato.

Por exemplo, se você quiser que a largura do widget seja redimensionável até uma célula em um Google Pixel 4, defina a minResizeWidth como no máximo 56 dp para garantir que o valor do atributo minResizeWidth seja menor que 57 dp, já que uma célula tem pelo menos 57 dp de largura no modo retrato. Da mesma forma, se você quiser que a altura do widget seja redimensionável em uma célula no mesmo dispositivo, defina o minResizeHeight como no máximo 50 dp para garantir que o valor do atributo minResizeHeight seja menor que 51 dp, já que uma célula tem pelo menos 51 dp de altura no modo paisagem.

Cada widget é redimensionável nos intervalos de tamanho entre os atributos minResizeWidth/minResizeHeight e maxResizeWidth/maxResizeHeight, o que significa que ele precisa se adaptar a qualquer intervalo de tamanho entre eles.

Por exemplo, para definir o tamanho padrão do widget no posicionamento, defina os seguintes atributos:

<appwidget-provider
    android:targetCellWidth="3"
    android:targetCellHeight="2"
    android:minWidth="180dp"
    android:minHeight="110dp">
</appwidget-provider>

Isso significa que o tamanho padrão do widget é de 3x2 células, conforme especificado pelos atributos targetCellWidth e targetCellHeight, ou 180 × 110 dp, conforme especificado por minWidth e minHeight para dispositivos com Android 11 ou versões anteriores. Nesse caso, o tamanho em células pode variar dependendo do dispositivo.

Além disso, para definir os intervalos de tamanho com suporte do widget, defina os seguintes atributos:

<appwidget-provider
    android:minResizeWidth="180dp"
    android:minResizeHeight="110dp"
    android:maxResizeWidth="530dp"
    android:maxResizeHeight="450dp">
</appwidget-provider>

Conforme especificado pelos atributos anteriores, a largura do widget é redimensionável de 180 dp a 530 dp, e a altura é redimensionável de 110 dp a 450 dp. O widget pode ser redimensionado de 3x2 a 5x2 células, desde que as seguintes condições estejam presentes:

Kotlin

val smallView = RemoteViews(context.packageName, R.layout.widget_weather_forecast_small)
val mediumView = RemoteViews(context.packageName, R.layout.widget_weather_forecast_medium)
val largeView = RemoteViews(context.packageName, R.layout.widget_weather_forecast_large)

val viewMapping: Map<SizeF, RemoteViews> = mapOf(
        SizeF(180f, 110f) to smallView,
        SizeF(270f, 110f) to mediumView,
        SizeF(270f, 280f) to largeView
)

appWidgetManager.updateAppWidget(appWidgetId, RemoteViews(viewMapping))

Java

RemoteViews smallView = 
    new RemoteViews(context.getPackageName(), R.layout.widget_weather_forecast_small);
RemoteViews mediumView = 
    new RemoteViews(context.getPackageName(), R.layout.widget_weather_forecast_medium);
RemoteViews largeView = 
    new RemoteViews(context.getPackageName(), R.layout.widget_weather_forecast_large);

Map<SizeF, RemoteViews> viewMapping = new ArrayMap<>();
viewMapping.put(new SizeF(180f, 110f), smallView);
viewMapping.put(new SizeF(270f, 110f), mediumView);
viewMapping.put(new SizeF(270f, 280f), largeView);
RemoteViews remoteViews = new RemoteViews(viewMapping);

appWidgetManager.updateAppWidget(id, remoteViews);

Suponha que o widget use os layouts responsivos definidos nos snippets de código anteriores. Isso significa que o layout especificado como R.layout.widget_weather_forecast_small é usado de 180 dp (minResizeWidth) x 110 dp (minResizeHeight) a 269 x 279 dp (próximos pontos de corte - 1). Da mesma forma, R.layout.widget_weather_forecast_medium é usado de 270 x 110 dp a 270 x 279 dp, e R.layout.widget_weather_forecast_large é usado de 270 x 280 dp a 530 dp (maxResizeWidth) x 450 dp (maxResizeHeight).

À medida que o usuário redimensiona o widget, a aparência dele muda para se adaptar a cada tamanho em células, conforme mostrado nos exemplos a seguir.

Exemplo de widget de clima no menor tamanho de grade 3x2. A interface mostra o nome do local (Tóquio), a temperatura (14°) e um símbolo indicando clima parcialmente nublado.
Figura 2. 3x2 R.layout.widget_weather_forecast_small.

Exemplo de widget de clima em um tamanho &quot;médio&quot; de 4x2. Redimensionar o widget
            dessa forma se baseia em toda a interface do usuário do tamanho anterior do widget
            e adiciona o rótulo &quot;Parcialmente nublado&quot; e uma previsão de temperaturas das
            16h às 19h.
Figura 3. 4x2 R.layout.widget_weather_forecast_medium.

Exemplo de widget de clima em um tamanho &quot;médio&quot; de 5x2. Redimensionar o widget
            dessa forma resulta na mesma interface que o tamanho anterior, exceto que ele é
            esticado por uma célula para ocupar mais espaço horizontal.
Figura 4. 5x2 R.layout.widget_weather_forecast_medium.

Exemplo de widget de clima em um tamanho &quot;grande&quot; de 5x3. Redimensionar o widget
            dessa forma se baseia em toda a interface dos tamanhos de widget anteriores
            e adiciona uma visualização dentro do widget com uma previsão do tempo
            para terça e quarta-feira. Símbolos que indicam clima ensolarado ou chuvoso
            e temperaturas máximas e mínimas para cada dia.
Figura 5. 5x3 R.layout.widget_weather_forecast_large.

Exemplo de widget de clima em um tamanho &quot;grande&quot; de 5x4. Redimensionar o widget
            dessa forma se baseia em toda a interface dos tamanhos de widget anteriores
            e adiciona quinta e sexta-feira (e os símbolos correspondentes
            que indicam o tipo de clima, bem como a temperatura máxima e mínima
            de cada dia).
Figura 6. 5x4 R.layout.widget_weather_forecast_large.