Stosowanie elastycznych układów widżetów

Na tej stronie opisujemy ulepszenia dotyczące rozmiaru widżetów i większą elastyczność wprowadzonych w Androidzie 12 (poziom API 31). Znajdziesz w nim też informacje o określaniu rozmiaru widżetu.

Używanie ulepszonych interfejsów API do obsługi rozmiarów i układów widżetów

Począwszy od Androida 12 (poziom interfejsu API 31) możesz udostępniać bardziej szczegółowe atrybuty rozmiaru i elastyczne układy, wykonując te czynności zgodnie z opisem w sekcjach poniżej:

  1. Określ dodatkowe ograniczenia dotyczące rozmiaru widżetów.

  2. Udostępnianie układów elastycznych lub ścisłych układów.

W poprzednich wersjach Androida można było pobrać zakresy rozmiarów widżetu za pomocą rozszerzeń OPTION_APPWIDGET_MIN_WIDTH, OPTION_APPWIDGET_MIN_HEIGHT, OPTION_APPWIDGET_MAX_WIDTH i OPTION_APPWIDGET_MAX_HEIGHT, a następnie oszacować rozmiar widżetu, ale ta logika nie zawsze działa. W przypadku widżetów kierowanych na Androida 12 lub nowszego zalecamy zastosowanie elastycznego lub precyzyjnego układu.

Określ dodatkowe ograniczenia rozmiaru widżetów

Android 12 dodaje interfejsy API, które zapewniają bardziej niezawodny rozmiar widżetu na różnych urządzeniach o różnych rozmiarach ekranu.

Oprócz istniejących atrybutów minWidth, minHeight, minResizeWidth i minResizeHeight użyj tych nowych atrybutów appwidget-provider:

  • targetCellWidth i targetCellHeight: określają docelowy rozmiar widżetu w postaci komórek siatki programu uruchamiającego. Jeśli atrybut jest zdefiniowany, używane są te atrybuty zamiast minWidth lub minHeight.

  • maxResizeWidth i maxResizeHeight: określają maksymalny rozmiar widżetu, który program uruchamiający pozwala użytkownikowi na zmianę rozmiaru.

Poniższy kod XML pokazuje, jak korzystać z atrybutów dobierania rozmiaru.

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

Zastosowanie układów elastycznych

Jeśli układ musi się zmieniać w zależności od rozmiaru widżetu, zalecamy utworzenie niewielkiego zestawu układów, z których każdy będzie przeznaczony dla różnych rozmiarów. Jeśli to niemożliwe, możesz podać układy na podstawie dokładnego rozmiaru widżetu w czasie działania, jak opisano na tej stronie.

Ta funkcja umożliwia płynniejsze skalowanie i ogólnie poprawia stan systemu, ponieważ system nie musi wybudzać aplikacji za każdym razem, gdy wyświetla widżet w innym rozmiarze.

Poniższy przykładowy kod pokazuje, jak udostępnić listę układów.

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

Załóżmy, że widżet ma te atrybuty:

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

Fragment kodu oznacza, że:

  • smallView obsługuje parametry od 160 dp (minResizeWidth) × 110 dp (minResizeHeight) do 160 dp × 199 dp (następny punkt odcięcia – 1 dp).
  • tallView obsługuje wymiary od 160 dp × 200 do 214 dp (następny punkt odcięcia – 1) × 200 dp.
  • wideView obsługuje wymiary od 215 dp × 110 dp (minResizeHeight) do 250 dp (maxResizeWidth) × 200 dp (maxResizeHeight).

Widżet musi obsługiwać rozmiary od minResizeWidth × minResizeHeight do maxResizeWidth × maxResizeHeight. W tym zakresie możesz określić, od którego punktu granicznego przełączać układy.

Przykład układu elastycznego
Rysunek 1. Przykład układu elastycznego.

Wybierz dokładny układ

Jeśli nie możesz zastosować małego zestawu układów elastycznych, możesz zamiast tego przygotować różne układy dostosowane do rozmiarów, w których wyświetla się widżet. Zwykle jest to 2 rozmiary dla telefonów (tryb pionowy i poziomy) i 4 rozmiary dla urządzeń składanych.

Aby wdrożyć to rozwiązanie, aplikacja musi wykonać te czynności:

  1. Przeładuj element AppWidgetProvider.onAppWidgetOptionsChanged(), który jest wywoływany po zmianie zestawu rozmiarów.

  2. Wywołaj AppWidgetManager.getAppWidgetOptions(), który zwraca Bundle z rozmiarami.

  3. Uzyskaj dostęp do klucza AppWidgetManager.OPTION_APPWIDGET_SIZES z systemu Bundle.

Ten przykładowy kod pokazuje, jak określić dokładne układy.

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

Określanie rozmiaru widżetu

Każdy widżet musi definiować atrybuty targetCellWidth i targetCellHeight dla urządzeń z Androidem 12 lub nowszym albo minWidth i minHeight w przypadku wszystkich wersji Androida. Musi to być minimalna ilość miejsca wykorzystywanego domyślnie. Gdy jednak użytkownicy dodają widżet do ekranu głównego, zajmuje on zwykle więcej miejsca niż określona przez Ciebie minimalna szerokość i wysokość.

Ekrany główne Androida to siatka dostępnych miejsc, w których można umieszczać widżety i ikony. Siatka może się różnić w zależności od urządzenia. Na przykład wiele telefonów oferuje siatkę 5 x 4, a na tabletach może być większa. Po dodaniu widżet jest rozciągnięty, aby zajmował w poziomie i pionie minimalną liczbę komórek wymaganych do spełnienia ograniczeń dotyczących targetCellWidth i targetCellHeight na urządzeniach z Androidem 12 lub nowszym albo ograniczeń minWidth i minHeight na urządzeniach z Androidem 11 (poziom interfejsu API 30) lub niższym.

Szerokość i wysokość komórki oraz rozmiar automatycznych marginesów stosowanych do widżetów mogą się różnić w zależności od urządzenia. Tabela poniżej pozwala w przybliżeniu oszacować minimalne wymiary widżetu w typowych telefonach z siatką 5 x 4 przy uwzględnieniu żądanej liczby zajętych komórek:

Liczba komórek (szerokość x wysokość) Dostępny rozmiar w trybie pionowym (dp) Dostępny rozmiar w trybie poziomym (dp)
1 × 1 57x102dp 127x51dp
2 × 1 130x102dp 269x51dp
3 × 1 203x102dp 412x51dp
10 × 15 cm 276x102dp 554x51dp
13 × 18 cm 349x102dp 697x51dp
13 × 28 cm 349x220dp 697x117dp
13 × 15 cm 349x337dp 697x184dp
13 × 15 cm 349x455dp 697x250dp
...
N × M (73 n – 16) x (118 m – 16) (142 n – 15) x (66 m–15)

Użyj rozmiarów komórek w trybie pionowym, aby podać wartości atrybutów minWidth, minResizeWidth i maxResizeWidth. W podobny sposób używaj rozmiarów komórek w trybie poziomym, aby podawać wartości w atrybutach minHeight, minResizeHeight i maxResizeHeight.

Wynika to z tego, że szerokość komórki jest zwykle mniejsza w trybie pionowym niż w trybie poziomym, a wysokość komórki jest zwykle mniejsza w trybie poziomym niż w trybie pionowym.

Jeśli na przykład na telefonie Google Pixel 4 chcesz zmniejszyć szerokość widżetu do jednej komórki, ustaw dla minResizeWidth wartość nie większą niż 56 dp, tak by wartość atrybutu minResizeWidth była mniejsza niż 57 dp, bo komórka ma co najmniej 57 dp w orientacji pionowej. I podobnie, jeśli chcesz, by wysokość widżetu można było zmieniać w jednej komórce na tym samym urządzeniu, musisz ustawić minResizeHeight na maksymalnie 50 dp, aby wartość atrybutu minResizeHeight była mniejsza niż 51 dp, bo 1 komórka ma wysokość co najmniej 51 dp w trybie poziomym.

Rozmiar każdego widżetu można zmienić w zakresie rozmiarów z atrybutów minResizeWidth/minResizeHeight i maxResizeWidth/maxResizeHeight, co oznacza, że musi dostosować się do dowolnego rozmiaru pomiędzy nimi.

Aby na przykład ustawić domyślny rozmiar widżetu w miejscu docelowym, możesz ustawić te atrybuty:

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

Oznacza to, że domyślny rozmiar widżetu to 3 x 2 komórki (zgodnie z atrybutami targetCellWidth i targetCellHeight) lub 180 × 110 dp (zgodnie z zasadami minWidth i minHeight) w przypadku urządzeń z Androidem 11 lub starszym. W tym drugim przypadku rozmiar komórek może się różnić w zależności od urządzenia.

Aby ustawić obsługiwane zakresy rozmiarów widżetu, możesz ustawić te atrybuty:

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

Zgodnie z poprzednimi atrybutami szerokość widżetu można zmieniać od 180 do 530 dp, a jego wysokość można zmieniać z 110 do 450 dp. Rozmiar widżetu można zmieniać z komórek 3 x 2 na 5 x 2, o ile są spełnione te warunki:

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

Załóżmy, że widżet korzysta z układów elastycznych zdefiniowanych w poprzednich fragmentach kodu. Oznacza to, że układ określony jako R.layout.widget_weather_forecast_small jest używany od 180 dp (minResizeWidth) x 110 dp (minResizeHeight) do 269 x 279 dp (następny próg – 1). Podobnie R.layout.widget_weather_forecast_medium jest używany od 270 x 110 dp do 270 x 279 dp, a R.layout.widget_weather_forecast_large jest stosowany od 270 x 280 do 530 dp (maxResizeWidth) x 450 dp (maxResizeHeight).

Gdy użytkownik zmienia rozmiar widżetu, jego wygląd dostosowuje się do rozmiaru komórek, jak widać w przykładach poniżej.

Przykładowy widżet pogody w najmniejszym rozmiarze siatki 3 x 2. Interfejs z nazwą lokalizacji (Tokio), temperaturą (14°) i symbolem wskazującym częściowe zachmurzenie.
Rysunek 2. 3x2 R.layout.widget_weather_forecast_small.

Przykładowy widżet pogody o średnim rozmiarze 4 x 2. Zmiana rozmiaru widżetu w ten sposób opiera się na całym UI z poprzedniego rozmiaru widżetu i dodaje etykietę „W większości chmury” oraz prognozę temperatur między 16:00 a 19:00.
Rysunek 3. 4x2 R.layout.widget_weather_forecast_medium.

Przykładowy widżet pogody w średnim rozmiarze 5 x 2. Zmiana rozmiaru widżetu w ten sposób powoduje taki sam interfejs jak w poprzednim rozmiarze, ale jest rozciągnięty o 1 komórkę, aby zajmował więcej miejsca w poziomie.
Rysunek 4. 5x2 R.layout.widget_weather_forecast_medium.

Przykładowy widżet pogody w dużym rozmiarze 5 x 3. Zmiana rozmiaru widżetu w ten sposób bazuje na wszystkich dotychczasowych rozmiarach widżetów w interfejsie i dodaje do widżetu widok z prognozą pogody na wtorek i środę. Symbole wskazujące słoneczną lub deszczową pogodę oraz wysokie i niskie temperatury w poszczególnych dniach.
Rysunek 5. 5 × 3 R.layout.widget_weather_forecast_large.

Przykładowy widżet pogody w dużym rozmiarze 5 x 4. Zmiana rozmiaru widżetu w ten sposób bazuje na wszystkich dotychczasowych rozmiarach widżetu i dodaje czwartek i piątek (oraz odpowiadające im symbole wskazujące rodzaj pogody oraz wysoką i niską temperaturę w poszczególnych dniach).
Rysunek 6. 5x4 R.layout.widget_weather_forecast_large.