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

Na tej stronie opisujemy ulepszenia dotyczące rozmiaru widżetów i większej elastyczności wprowadzone w Androidzie 12 (poziom API 31). Znajdziesz tam też instrukcje dotyczące określania 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 podawać bardziej dokładne atrybuty rozmiarów i elastyczne układy, wykonując czynności opisane w następnych sekcjach:

  1. Określ dodatkowe ograniczenia rozmiaru widżetu.

  2. udostępnianie elastycznego układu strony lub dokładnego układu strony;

W poprzednich wersjach Androida można uzyskać zakresy rozmiarów widżetu za pomocą dodatkowych informacji OPTION_APPWIDGET_MIN_WIDTH, OPTION_APPWIDGET_MIN_HEIGHT, OPTION_APPWIDGET_MAX_WIDTHOPTION_APPWIDGET_MAX_HEIGHT, a następnie oszacować rozmiar widżetu, ale ta logika nie działa we wszystkich sytuacjach. W przypadku widżetów kierowanych na Androida 12 lub nowszego zalecamy dopasowywanie lub dokładne układy.

Określanie dodatkowych ograniczeń rozmiaru widżetu

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 są zdefiniowane, te atrybuty są używane zamiast atrybutów minWidth lub minHeight.

  • maxResizeWidthmaxResizeHeight: określają maksymalny rozmiar, do którego użytkownik może zmienić rozmiar widżetu.

Poniższy kod XML pokazuje, jak używać atrybutów rozmiarów.

<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 widgeta, zalecamy utworzenie niewielkiego zestawu układów, z których każdy będzie odpowiedni dla określonego zakresu rozmiarów. Jeśli to nie jest możliwe, możesz podać układy oparte na dokładnym rozmiarze widgeta w czasie wykonywania, jak opisano na tej stronie.

Ta funkcja zapewnia płynniejsze skalowanie i ogólne lepsze działanie systemu, ponieważ nie musi on budzić aplikacji za każdym razem, gdy wyświetla widżet w innej wielkości.

Poniższy przykład kodu pokazuje, jak podać 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 wartości od 160 x 200 dp do 214 dp (następny punkt graniczny – 1) x 200 dp.
  • wideView obsługuje formaty od 215 dp x 110 dp (minResizeHeight) do 250 dp (maxResizeWidth) x 200 dp (maxResizeHeight).

Widżet musi obsługiwać rozmiary od minResizeWidth × minResizeHeight do maxResizeWidth × maxResizeHeight. W tym zakresie możesz określić punkt graniczny, w którym nastąpi przełączenie układu.

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

Podawanie dokładnych układów

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 są to 2 rozmiary na telefony (w orientacji pionowej i poziomej) oraz 4 rozmiary na urządzenia składane.

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

  1. Overload AppWidgetProvider.onAppWidgetOptionsChanged(), który jest wywoływany, gdy zmienia się zestaw rozmiarów.

  2. Wywołaj funkcję AppWidgetManager.getAppWidgetOptions(), która zwraca tablicę Bundle zawierającą rozmiary.

  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 zdefiniować targetCellWidth i targetCellHeight dla urządzeń z Androidem 12 lub nowszym (lub minWidth i minHeight w przypadku wszystkich wersji Androida) – aby wskazać minimalną ilość miejsca wykorzystywanego domyślnie. Jednak gdy użytkownicy dodają widżet do ekranu głównego, zajmuje on więcej niż minimalną szerokość i wysokość określone przez Ciebie.

Ekrany główne Androida oferują użytkownikom siatkę dostępnych miejsc, w których mogą umieszczać widżety i ikony. Ta 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. Gdy dodasz widżet, zostanie on rozciągnięty, aby zajmował minimalną liczbę komórek w poziomie poziomym i pionowym, wymaganą do spełnienia ograniczeń targetCellWidthtargetCellHeight na urządzeniach z Androidem 12 lub nowszym albo minWidthminHeight na urządzeniach z Androidem 11 (poziom API 30) lub starszym.

Zarówno szerokość, jak i wysokość komórki oraz rozmiar automatycznych marginesów stosowanych w widżetach mogą się różnić w zależności od urządzenia. Za pomocą poniższej tabeli możesz w przybliżeniu oszacować minimalne wymiary widżetu w typowym telefonie z siatką 5 x 4, biorąc pod uwagę liczbę zajętych komórek siatki:

Liczba komórek (szerokość x wysokość) Dostępny rozmiar w panoramie (dp) Dostępny rozmiar w trybie poziomym (dp)
1 x 1 57x102dp 127x51dp
2 × 1 130x102dp 269 x 51 dp
3 x 1 203 x 102 pikseli 412 x 51 dp
4 x 1 276x102dp 554x51dp
5 x 1 349 x 102 dp 697x51dp
13 × 28 cm 349 x 220 dp 697 x 117 dp
5 x 3 349x337dp 697x184dp
5 x 4 349 x 455 dp 697 x 250 pikseli
...
N × M (73n – 16) x (118m – 16) (142n-15) x (66m-15)

Użyj rozmiarów komórek w trybie poziomym, 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.

Dzieje się tak, ponieważ szerokość komórki jest zazwyczaj mniejsza w trybie pionowym niż w trybie poziomym, a podobnie wysokość komórki jest zazwyczaj 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ść maksymalnie 56 dp, aby wartość atrybutu minResizeWidth była mniejsza niż 57 dp, bo komórka ma co najmniej 57 dp w orientacji pionowej. Jeśli chcesz, aby wysokość widżetu można było zmieniać w ramach jednej komórki na tym samym urządzeniu, musisz ustawić wartość atrybutu minResizeHeight na co najwyżej 50 dp, aby mieć pewność, że wartość atrybutu minResizeHeight jest mniejsza niż 51 dp – ponieważ w trybie poziomym wysokość jednej komórki wynosi co najmniej 51 dp.

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 targetCellWidthtargetCellHeight, lub 180 x 110 dp, zgodnie z atrybutami minWidthminHeight na urządzeniach z Androidem 11 lub niższym. 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 też 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 wysokość – od 110 do 450 dp. Widżet można wtedy zmieniać z 3 na 2 komórki i odwrotnie, o ile spełnione są te warunki:

  • Urządzenie ma siatkę 5 x 4.
  • Mapowanie liczby komórek i dostępnej wielkości w dps jest zgodne z tabelą z szacunkiem minimalnych wymiarów na tej stronie.
  • Widżet dostosuje się do tego zakresu rozmiarów.

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 widget używa elastycznych układów zdefiniowanych w poprzednich fragmentach kodu. Oznacza to, że układ określony jako R.layout.widget_weather_forecast_small jest używany w zakresie od 180 dp (minResizeWidth) x 110 dp (minResizeHeight) do 269 x 279 dp (kolejne punkty przecięcia – 1). Podobnie, R.layout.widget_weather_forecast_medium jest używany w zakresie 270 x 110 dp do 270 x 279 dp, a R.layout.widget_weather_forecast_large – w zakresie 270 x 280 dp do 530 dp (maxResizeWidth) x 450 dp (maxResizeHeight).

Gdy użytkownik zmienia rozmiar widżetu, jego wygląd zmienia się, aby dopasować się do każdego rozmiaru w komórkach, jak pokazano w poniższych przykładach.

Przykład widżetu pogody w najmniejszym rozmiarze 3 x 2. Interfejs pokazuje nazwę lokalizacji (Tokio), temperaturę (14°) i symbol wskazujący na pogodę z umiarkowanym zachmurzeniem.
Rysunek 2. 3 x 2 R.layout.widget_weather_forecast_small.

Przykład widżetu pogody w rozmiarze „średni” 4 x 2. Zmiana rozmiaru widżetu w ten sposób bazuje na całym interfejsie użytkownika w stosunku do poprzedniego rozmiaru widżetu i dodaje etykietę „W większości chmury” oraz prognozę temperatur między 16:00 a 19:00.
Rysunek 3. 4 x 2 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, że interfejs wygląda tak samo jak w poprzednim rozmiarze, ale jest rozciągnięty o długość jednej komórki, aby zajmował więcej miejsca na poziomo.
Rysunek 4. 5 x 2 R.layout.widget_weather_forecast_medium

Przykład widżetu pogody w dużej wersji 5 x 3. Zmiana rozmiaru widżetu w ten sposób opiera się na całym interfejsie z poprzednich rozmiarów widżetu i dodaje widok z prognozą pogody na wtorek i środę. Symbole wskazujące na pogodę słoneczną lub deszczową oraz najwyższe i najniższe temperatury w każdym dniu.
Rysunek 5. 5 x 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 opiera się na wszystkich elementach interfejsu z poprzednich rozmiarów widżetu i dodaje czwartek i piątek (oraz odpowiednie symbole) z podziałem na typ pogody oraz wysoką i niską temperaturę dla każdego dnia.
Rysunek 6. 5x4 R.layout.widget_weather_forecast_large.