Fornire layout flessibili per i widget

In questa pagina vengono descritti i perfezionamenti per le dimensioni dei widget e la maggiore flessibilità introdotti in Android 12 (livello API 31). Descrive inoltre come determinare le dimensioni del widget.

Usa API migliorate per dimensioni e layout dei widget

A partire da Android 12 (livello API 31), puoi fornire attributi relativi alle dimensioni più perfezionati e layout flessibili procedendo nel seguente modo, come descritto nelle sezioni riportate di seguito:

  1. Specifica vincoli aggiuntivi per le dimensioni del widget.

  2. Fornire layout adattabili o layout esatti.

Nelle versioni precedenti di Android, è possibile ottenere gli intervalli di dimensioni di un widget utilizzando gli extra OPTION_APPWIDGET_MIN_WIDTH, OPTION_APPWIDGET_MIN_HEIGHT, OPTION_APPWIDGET_MAX_WIDTH e OPTION_APPWIDGET_MAX_HEIGHT e quindi stimare le dimensioni del widget, ma questa logica non funziona in tutte le situazioni. Per i widget che hanno come target Android 12 o versioni successive, consigliamo di fornire layout adattabili o layout esatti.

Specifica vincoli aggiuntivi relativi alle dimensioni dei widget

Android 12 aggiunge API che consentono di garantire che le dimensioni del widget siano più affidabili su dispositivi diversi con schermi di varie dimensioni.

Oltre agli attributi minWidth, minHeight, minResizeWidth e minResizeHeight esistenti, utilizza i seguenti nuovi attributi appwidget-provider:

  • targetCellWidth e targetCellHeight: definisci le dimensioni di destinazione del widget in termini di celle della griglia Avvio app. Se definiti, questi attributi vengono utilizzati al posto di minWidth o minHeight.

  • maxResizeWidth e maxResizeHeight: definisci le dimensioni massime a cui l'Avvio app consente all'utente di ridimensionare il widget.

Il seguente XML mostra come utilizzare gli attributi di taglia.

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

Fornisci layout adattabili

Se il layout deve cambiare in base alle dimensioni del widget, ti consigliamo di creare un piccolo insieme di layout, ciascuno valido per un intervallo di dimensioni. Se questo non è possibile, un'altra opzione è fornire layout basati sulle dimensioni esatte del widget in fase di runtime, come descritto in questa pagina.

Questa funzionalità consente una scalabilità più fluida e un migliore integrità complessiva del sistema, dal momento che il sistema non deve riattivare l'app ogni volta che viene visualizzato il widget di una dimensione diversa.

Il seguente esempio di codice mostra come fornire un elenco di layout.

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);
}

Supponiamo che il widget abbia i seguenti attributi:

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

Lo snippet di codice precedente indica quanto segue:

  • smallView supporta una risoluzione da 160 dp (minResizeWidth) × 110 dp (minResizeHeight) a 160 dp × 199 dp (punto di interruzione successivo: 1 dp).
  • tallView supporta una risoluzione da 160 dp × 200 dp a 214 dp (punto di interruzione successivo - 1) × 200 dp.
  • wideView supporta da 215 dp × 110 dp (minResizeHeight) a 250 dp (maxResizeWidth) × 200 dp (maxResizeHeight).

Il widget deve supportare l'intervallo di dimensioni compreso tra minResizeWidth × minResizeHeight e maxResizeWidth × maxResizeHeight. All'interno di questo intervallo, puoi decidere il punto di interruzione per cambiare layout.

Esempio di layout adattabile
Figura 1. Esempio di layout adattabile.

Fornire layout esatti

Se non è possibile realizzare un piccolo insieme di layout adattabili, puoi comunque fornire layout diversi in base alle dimensioni in cui viene mostrato il widget. In genere sono disponibili due dimensioni per gli smartphone (in modalità verticale e orizzontale) e quattro per i pieghevoli.

Per implementare questa soluzione, la tua app deve svolgere i seguenti passaggi:

  1. Sovraccarico AppWidgetProvider.onAppWidgetOptionsChanged(), che viene chiamato quando cambia l'insieme di dimensioni.

  2. Chiamare AppWidgetManager.getAppWidgetOptions(), che restituisce un elemento Bundle contenente le dimensioni.

  3. Accedi alla chiave AppWidgetManager.OPTION_APPWIDGET_SIZES dal Bundle.

Il seguente esempio di codice mostra come fornire layout esatti.

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) { }

Determinare le dimensioni del widget

Ogni widget deve definire i valori targetCellWidth e targetCellHeight per i dispositivi con Android 12 o versioni successive oppure minWidth e minHeight per tutte le versioni di Android e indicano la quantità minima di spazio che consuma per impostazione predefinita. Tuttavia, quando gli utenti aggiungono un widget alla schermata Home, in genere questo occupa più della larghezza e dell'altezza minime specificate.

Le schermate Home di Android offrono agli utenti una griglia di spazi disponibili in cui possono posizionare widget e icone. Questa griglia può variare in base al dispositivo. Ad esempio, molti telefoni hanno una griglia 5 x 4, mentre i tablet possono avere una griglia più grande. Quando viene aggiunto, il widget viene esteso in modo da occupare il numero minimo di celle, orizzontalmente e verticalmente, necessario per soddisfare i vincoli di targetCellWidth e targetCellHeight sui dispositivi con Android 12 o versioni successive oppure dei vincoli minWidth e minHeight sui dispositivi con Android 11 (livello API 30) o versioni precedenti.

Sia la larghezza e l'altezza di una cella sia le dimensioni dei margini automatici applicati ai widget possono variare da un dispositivo all'altro. Utilizza la seguente tabella per stimare approssimativamente le dimensioni minime del widget in un tipico smartphone con griglia 5 x 4, dato il numero desiderato di celle della griglia occupate:

Numero di celle (larghezza x altezza) Dimensioni disponibili in modalità verticale (dp) Dimensioni disponibili in modalità Orizzontale (dp)
1x1 57x102dp 127x51dp
2x1 130x102dp 269x51dp
3x1 203x102dp 412x51dp
4x1 276x102dp 554x51dp
5x1 349x102dp 697x51dp
5x2 349x220dp 697x117dp
5x3 349x337dp 697x184dp
5x4 349x455dp 697x250dp
...
n x m (73n - 16) x (118m - 16) (142n - 15) x (66m - 15)

Utilizza le dimensioni delle celle in modalità verticale per definire i valori che fornisci per gli attributi minWidth, minResizeWidth e maxResizeWidth. Analogamente, utilizza le dimensioni delle celle in modalità Orizzontale per definire i valori che fornisci per gli attributi minHeight, minResizeHeight e maxResizeHeight.

Il motivo è che la larghezza della cella è generalmente minore in modalità verticale che in modalità orizzontale e, allo stesso modo, l'altezza della cella è in genere più ridotta in modalità orizzontale rispetto a quella in modalità verticale.

Ad esempio, se vuoi che la larghezza del widget sia ridimensionabile fino a una cella su Google Pixel 4, devi impostare minResizeWidth al massimo su 56 dp per assicurarti che il valore dell'attributo minResizeWidth sia inferiore a 57 dp, perché una cella ha una larghezza di almeno 57 dp in verticale. Allo stesso modo, se vuoi che l'altezza del widget sia ridimensionabile in una cella dello stesso dispositivo, devi impostare minResizeHeight al massimo a 50 dp per assicurarti che il valore dell'attributo minResizeHeight sia inferiore a 51 dp, perché una cella ha un'altezza di almeno 51 dp in modalità Orizzontale.

Ogni widget è ridimensionabile all'interno degli intervalli di dimensioni compresi tra gli attributi minResizeWidth/minResizeHeight e maxResizeWidth/maxResizeHeight, il che significa che deve adattarsi a qualsiasi intervallo di dimensioni tra di loro.

Ad esempio, per impostare la dimensione predefinita del widget nel posizionamento, puoi impostare i seguenti attributi:

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

Ciò significa che la dimensione predefinita del widget è 3 x 2 celle, come specificato dagli attributi targetCellWidth e targetCellHeight, oppure 180 × 110 dp, come specificato da minWidth e minHeight per i dispositivi con Android 11 o versioni precedenti. Nel secondo caso, la dimensione delle celle può variare in base al dispositivo.

Inoltre, per impostare gli intervalli di dimensioni supportati dal widget, puoi impostare i seguenti attributi:

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

Come specificato dagli attributi precedenti, la larghezza del widget è ridimensionabile da 180 dp a 530 dp e l'altezza è ridimensionabile da 110 dp a 450 dp. Il widget è quindi ridimensionabile da 3 x 2 a 5 x 2 celle, purché siano presenti le seguenti condizioni:

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);

Supponiamo che il widget utilizzi i layout adattabili definiti negli snippet di codice precedenti. Ciò significa che il layout specificato come R.layout.widget_weather_forecast_small viene utilizzato da 180 dp (minResizeWidth) x 110 dp (minResizeHeight) a 269 x 279 dp (punti di interruzione successivi - 1). Analogamente, R.layout.widget_weather_forecast_medium viene utilizzata da 270 x 110 dp a 270 x 279 dp e R.layout.widget_weather_forecast_large viene utilizzata da 270 x 280 dp a 530 dp (maxResizeWidth) x 450 dp (maxResizeHeight).

Man mano che l'utente ridimensiona il widget, il suo aspetto cambia per adattarsi a ogni dimensione nelle celle, come mostrato nei seguenti esempi.

Esempio di widget meteo nella dimensione più piccola della griglia 3 x 2. La UI mostra
            il nome della posizione (Tokyo), la temperatura (14°) e il simbolo che indica
            tempo parzialmente nuvoloso.
Figura 2. 3x2 R.layout.widget_weather_forecast_small.

Esempio di widget meteo in formato 4 x 2 &quot;medio&quot;. Il ridimensionamento del widget in questo modo si basa su tutta l&#39;interfaccia utente della dimensione precedente del widget e aggiunge l&#39;etichetta &quot;Per lo più nuvoloso&quot; e una previsione delle temperature dalle 16:00 alle 19:00.
Figura 3. 4x2 R.layout.widget_weather_forecast_medium.

Esempio di widget meteo in formato 5 x 2 &quot;medie&quot;. Se ridimensionando il widget in questo modo, il risultato sarà la stessa UI di quella precedente, ad eccezione del fatto che viene ampliata di una cella per occupare più spazio orizzontale.
Figura 4. 5x2 R.layout.widget_weather_forecast_medium.

Esempio di widget meteo in formato &quot;grandi&quot; 5 x 3. Il ridimensionamento del widget in questo modo si basa su tutte le UI delle dimensioni precedenti dei widget e aggiunge una visualizzazione all&#39;interno del widget contenente una previsione del meteo di martedì e mercoledì. Simboli che indicano tempo soleggiato o piovoso
            e temperature massime e basse per ciascun giorno.
Figura 5. 5x3 R.layout.widget_weather_forecast_large.

Esempio di widget meteo in formato &quot;grandi&quot; 5 x 4. Il ridimensionamento del widget in questo modo si basa su tutte le UI delle dimensioni precedenti dei widget e aggiunge giovedì e venerdì (e i simboli corrispondenti che indicano il tipo di meteo e la temperatura massima e minima per ogni giorno).
Figura 6. 5x4 R.layout.widget_weather_forecast_large.