Fornire layout flessibili per i widget

Prova Compose
Jetpack Compose è il toolkit per la UI consigliato per Android. Scopri come creare widget utilizzando API in stile Compose.

Questa pagina descrive i perfezionamenti per il dimensionamento dei widget e la maggiore flessibilità introdotti in Android 12 (livello API 31). Descrive inoltre come determinare le dimensioni del widget.

Utilizza API migliorate per le dimensioni e i layout dei widget

A partire da Android 12 (livello API 31), puoi fornire attributi di dimensione più precisi e layout flessibili seguendo questi passaggi, come descritto nelle sezioni seguenti:

  1. Specifica ulteriori vincoli di dimensionamento 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, 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 esatti.

Specifica ulteriori vincoli di dimensionamento del widget

Android 12 aggiunge API che ti consentono di assicurarti che il tuo widget venga ridimensionato in modo più affidabile su diversi dispositivi con schermi di varie dimensioni.

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

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

  • maxResizeWidth e maxResizeHeight: definiscono le dimensioni massime consentite dal programma di avvio per il ridimensionamento del widget da parte dell'utente.

Il seguente XML mostra come utilizzare gli attributi di dimensionamento.

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
  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, ognuno valido per un intervallo di dimensioni. Se 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'integrità complessiva del sistema migliore, perché il sistema non deve riattivare l'app ogni volta che visualizza il widget in dimensioni diverse.

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

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

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

Supponiamo che il widget abbia i seguenti attributi:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minResizeWidth="160dp"
    android:minResizeHeight="110dp"
    android:maxResizeWidth="250dp"
    android:maxResizeHeight="200dp">
</appwidget-provider>

Lo snippet di codice precedente significa quanto segue:

  • smallView supporta da 160 dp (minResizeWidth) × 110 dp (minResizeHeight) a 160 dp × 199 dp (punto di interruzione successivo - 1 dp).
  • tallView supporta 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 da minResizeWidth × minResizeHeight a 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.

Fornisci layout esatti

Se non è possibile utilizzare un piccolo insieme di layout adattabili, puoi fornire layout diversi personalizzati in base alle dimensioni in cui viene mostrato il widget. In genere, si tratta di due dimensioni per gli smartphone (modalità verticale e modalità Orizzontale) e quattro dimensioni per i pieghevoli.

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

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

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

  3. Accedi alla chiave AppWidgetManager.OPTION_APPWIDGET_SIZES da Bundle.

Il seguente esempio di codice mostra come fornire layout esatti.

override fun onAppWidgetOptionsChanged(
    context: Context,
    appWidgetManager: AppWidgetManager,
    id: Int,
    newOptions: Bundle?
) {
    super.onAppWidgetOptionsChanged(context, appWidgetManager, id, newOptions)
    // Get the new sizes.
    val sizes = newOptions?.let {
        BundleCompat.getParcelableArrayList(
            it,
            AppWidgetManager.OPTION_APPWIDGET_SIZES,
            SizeF::class.java
        )
    }
    // 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 {
}

Determina le dimensioni del widget

Ogni widget deve definire un targetCellWidth e un targetCellHeight per i dispositivi con Android 12 o versioni successive oppure un minWidth e un minHeight per tutte le versioni di Android, indicando la quantità minima di spazio che consuma per impostazione predefinita. Tuttavia, quando gli utenti aggiungono un widget alla schermata Home, questo occupa in genere 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 smartphone offrono una griglia 5x4, mentre i tablet possono offrire una griglia più grande. Quando il widget viene aggiunto, viene allungato per occupare il numero minimo di celle, in orizzontale e in verticale, necessarie per soddisfare i vincoli per targetCellWidth e targetCellHeight sui dispositivi con Android 12 o versioni successive o i vincoli minWidth e minHeight sui dispositivi con Android 11 (livello API 30) o versioni precedenti.

Le dimensioni delle celle e i margini automatici variano a seconda del dispositivo. Utilizza la seguente tabella per stimare le dimensioni minime del widget su un tipico smartphone con griglia 5x4, in base al numero di celle della griglia occupate che vuoi:

Numero di celle (larghezza x altezza) Dimensioni disponibili in modalità Ritratto (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à Ritratto per determinare i valori da fornire per gli attributi minWidth, minResizeWidth e maxResizeWidth. Analogamente, utilizza le dimensioni delle celle in modalità Orizzontale per determinare i valori da fornire per gli attributi minHeight, minResizeHeight e maxResizeHeight.

Questo perché la larghezza della cella è in genere inferiore in modalità Ritratto rispetto alla modalità Orizzontale e, analogamente, l'altezza della cella è in genere inferiore in modalità Orizzontale rispetto alla modalità Ritratto.

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

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

Ad esempio, per impostare le dimensioni predefinite del widget al momento del posizionamento, puoi impostare i seguenti attributi:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:targetCellWidth="3"
    android:targetCellHeight="2"
    android:minWidth="180dp"
    android:minHeight="110dp">
</appwidget-provider>

Ciò significa che le dimensioni predefinite del widget sono 3x2 celle, come specificato dagli attributi targetCellWidth e targetCellHeight, o 180 x 110 dp, come specificato da minWidth e minHeight per i dispositivi con Android 11 o versioni precedenti. In quest'ultimo caso, le dimensioni in celle possono variare a seconda del dispositivo.

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

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    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 3x2 a 5x2 celle, a condizione che siano presenti le seguenti condizioni:

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

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 utilizzato da 270 x 110 dp a 270 x 279 dp, mentre R.layout.widget_weather_forecast_large viene utilizzato da 270 x 280 dp a 530 dp (maxResizeWidth) x 450 dp (maxResizeHeight).

Quando l'utente ridimensiona il widget, il suo aspetto cambia per adattarsi a ogni dimensione in celle, come mostrato negli esempi seguenti.

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

Esempio di widget meteo di dimensioni &quot;medie&quot; 4x2. Il ridimensionamento del widget in questo modo si basa su tutta l'UI della dimensione precedente del widget e aggiunge l'etichetta &quot;Molto 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 di dimensioni &quot;medie&quot; 5x2. Il ridimensionamento del widget in questo modo genera la stessa UI della dimensione precedente, ad eccezione del fatto che viene allungato di una cella per occupare più spazio orizzontale.
Figura 4. 5x2 R.layout.widget_weather_forecast_medium.

Esempio di widget meteo di dimensioni &quot;grandi&quot; 5x3. Il ridimensionamento del widget
            in questo modo si basa su tutta l'UI delle dimensioni precedenti del widget
            e aggiunge una visualizzazione all'interno del widget contenente una previsione del meteo
            di martedì e mercoledì. Simboli che indicano tempo soleggiato o piovoso e temperature massime e minime per ogni giorno.
Figura 5. 5x3 R.layout.widget_weather_forecast_large.

Esempio di widget meteo di dimensioni &quot;grandi&quot; 5x4. Il ridimensionamento del widget in questo modo si basa su tutta l'UI delle dimensioni precedenti del widget e aggiunge giovedì e venerdì (e i relativi simboli che indicano il tipo di meteo, nonché la temperatura massima e minima per ogni giorno).
Figura 6. 5x4 R.layout.widget_weather_forecast_large.