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

Na tej stronie opisujemy ulepszenia w zakresie rozmiaru widżetów i zwiększające jej elastyczność wprowadzone w Androidzie 12 (poziom interfejsu API 31). Wyjaśniamy też, jak określić rozmiar widżetu.

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

Począwszy od Androida 12 (poziom interfejsu API 31) możesz dodawać bardziej precyzyjne atrybuty rozmiaru i elastyczne układy, wykonując te czynności w sposób opisany w poniższych sekcjach:

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

  2. stosowanie układów elastycznych lub ścisłych układów;

W poprzednich wersjach Androida można było ustalić zakresy rozmiarów widżetu, korzystając z elementów 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 metoda nie działa w niektórych sytuacjach. W przypadku widżetów kierowanych na Androida 12 lub nowszego zalecamy stosowanie elastycznych lub ścisłych układów.

Określanie dodatkowych ograniczeń rozmiaru widżetów

W Androidzie 12 dodajemy interfejsy API, dzięki którym rozmiar widżetu działa w stabilny sposób na różnych urządzeniach o różnych rozmiarach ekranów.

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

  • targetCellWidth i targetCellHeight: określ docelowy rozmiar widżetu na podstawie komórek siatki programu uruchamiającego. Jeśli określisz atrybuty, będą one używane zamiast minWidth lub minHeight.

  • maxResizeWidth i maxResizeHeight: określ maksymalny rozmiar, do którego program uruchamiający pozwala użytkownikowi zmienić rozmiar widżetu.

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

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

Dodaj układy elastyczne

Jeśli układ musi się zmieniać w zależności od rozmiaru widżetu, zalecamy utworzenie małego zestawu układów o różnych rozmiarach. Jeśli nie jest to możliwe, możesz też podać układy na podstawie dokładnego rozmiaru widżetu w czasie działania, jak opisano na tej stronie.

Ta funkcja zapewnia płynniejsze skalowanie i ogólnie poprawia kondycję 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 wyświetlić 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>

Poprzedni fragment kodu:

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

Widżet musi obsługiwać zakres rozmiarów od minResizeWidth × minResizeHeight do maxResizeWidth × maxResizeHeight. W tym zakresie możesz określić punkt odcięcia, aby zmienić układ.

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

Podaj dokładne układy

Jeśli mały zestaw układów elastycznych nie jest dostępny, możesz zastosować różne układy dostosowane do rozmiarów, w których wyświetla się widżet. Zwykle są to 2 rozmiary dla telefonów (orientacja pionowa i pozioma) oraz 4 rozmiary dla urządzeń składanych.

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

  1. Przeciążenie AppWidgetProvider.onAppWidgetOptionsChanged(), które jest wywoływane po zmianie zbioru rozmiarów.

  2. Wywołanie funkcji AppWidgetManager.getAppWidgetOptions(), która zwraca wartość Bundle zawierającą rozmiary.

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

Poniższy 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ć targetCellWidth i targetCellHeight w przypadku urządzeń z Androidem 12 lub nowszym albo minWidth i minHeight w przypadku wszystkich wersji Androida, które domyślnie wskazują minimalną ilość miejsca zajmowanego przez urządzenie. Jednak gdy użytkownicy dodają widżet do ekranu głównego, zajmuje on zwykle więcej niż określona przez Ciebie minimalna szerokość i wysokość.

Ekrany główne Androida to siatka 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, np. w wielu słuchawkach jest w siatce 5 x 4, a tablety – większą. Po dodaniu widżetu zostaje on rozciągnięty tak, aby zajmował minimalną liczbę komórek w poziomie i w pionie wymaganą do spełnienia ograniczeń dotyczących targetCellWidth i targetCellHeight na urządzeniach z Androidem 12 lub nowszym albo 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. W tabeli poniżej znajdziesz oszacowanie minimalnych wymiarów widżetu w typowej słuchawce z siatką o wymiarach 5 × 4, z uwzględnieniem liczby żądanych komórek siatki:

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

Używaj rozmiarów komórek w trybie portretowym, aby przekazywać wartości atrybutów minWidth, minResizeWidth i maxResizeWidth. Podobnie używaj rozmiarów komórek w trybie poziomym, aby podać wartości atrybutów minHeight, minResizeHeight i maxResizeHeight.

Powodem jest to, że w orientacji pionowej szerokość komórki jest zwykle mniejsza niż w trybie poziomym, a wysokość komórki jest zwykle mniejsza w trybie poziomym niż pionowym.

Jeśli na przykład chcesz, aby na urządzeniu Google Pixel 4 szerokość widżetu można było zmienić w dół do jednej komórki, ustaw minResizeWidth na nie większą niż 56 dp. Dzięki temu wartość atrybutu minResizeWidth będzie mniejsza niż 57 dp, bo komórka ma co najmniej 57 dp szerokości w orientacji pionowej. I podobnie, jeśli chcesz, aby wysokość widżetu można było zmienić w jednej komórce na tym samym urządzeniu, ustaw minResizeHeight na maksymalnie 50 dp. Dzięki temu wartość atrybutu minResizeHeight będzie mniejsza niż 51 dp, bo jedna komórka ma co najmniej 51 dp w trybie poziomym.

Rozmiar każdego widżetu można zmieniać w zakresie rozmiarów od atrybutów minResizeWidth/minResizeHeight do maxResizeWidth/maxResizeHeight, co oznacza, że musi się dostosować do dowolnych zakresów rozmiarów między nimi.

Aby na przykład ustawić domyślny rozmiar widżetu w miejscu docelowym, możesz użyć tych atrybutów:

<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 określone za pomocą atrybutów targetCellWidth i targetCellHeight – lub 180 × 110 dp w przypadku urządzeń z Androidem 11 lub starszym, zgodnie z zasadami minWidth i minHeight. 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>

Jak określono w poprzednich atrybutach, szerokość widżetu można zmienić w zakresie od 180 dp do 530 dp, a jego wysokość można zmieniać z zakresu od 110 dp do 450 dp. Następnie można zmieniać rozmiar widżetu z 3 x 2 na 5 x 2, pod warunkiem że spełnione są 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 używa układów elastycznych zdefiniowanych we wcześniejszych 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ępne punkty odcięcia – 1). Podobnie parametr R.layout.widget_weather_forecast_medium jest używany w zakresie od 270 x 110 dp do 270 x 279 dp, a R.layout.widget_weather_forecast_large jest używany w zakresie od 270 x 280 dp do 530 dp (maxResizeWidth) x 450 dp (maxResizeHeight).

Gdy użytkownik zmieni rozmiar widżetu, jego wygląd będzie się zmieniał w zależności od rozmiaru w komórkach, jak pokazano w poniższych przykładach.

Przykładowy widżet pogody w siatce o najmniejszym rozmiarze 3 x 2. Interfejs zawiera nazwę lokalizacji (Tokio), temperaturę (14°) i symbol oznaczający częściowo zachmurzenie.
Rysunek 2. 3 × 2 R.layout.widget_weather_forecast_small.

Przykładowy widżet pogody w rozmiarze „średnim” 4 x 2. Ta zmiana rozmiaru widżetu zostanie zastosowana do wszystkich elementów interfejsu użytkownika z poprzedniego rozmiaru widżetu i doda etykietę „W większości zachmurzenie” i prognozę temperatury od 16:00 do 19:00.
Rysunek 3. 4 × 2 R.layout.widget_weather_forecast_medium.

Przykładowy widżet pogody w rozmiarze „średnim” 5 x 2. Zmiana rozmiaru widżetu w ten sposób powoduje wyświetlenie interfejsu użytkownika tak samo jak w poprzednim rozmiarze, z tą różnicą, że jest on rozciągnięty o jedną długość komórki, aby zajmował więcej miejsca w poziomie.
Rysunek 4. 5 × 2 R.layout.widget_weather_forecast_medium.

Przykładowy widżet pogody w rozmiarze 5 x 3 „duży”. Ta zmiana rozmiaru widżetu zostanie zastosowana do wszystkich elementów interfejsu użytkownika z poprzednich rozmiarów widżetu i spowoduje dodanie w widżecie widoku 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 rozmiarze 5 x 4 „duży”. Ta zmiana rozmiaru widżetu zostanie zastosowana do wszystkich elementów interfejsu użytkownika z poprzednich rozmiarów widżetu i zostaną dodane czwartek i piątek (oraz odpowiadające im symbole do rodzaju pogody oraz temperatury maksymalnej i minimalnej w poszczególnych dniach).
Rysunek 6. 5 × 4 R.layout.widget_weather_forecast_large.