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

Wypróbuj Compose
Jetpack Compose to zalecany zestaw narzędzi do tworzenia interfejsu na Androidzie. Dowiedz się, jak tworzyć widżety za pomocą interfejsów API w stylu Compose.

Na tej stronie opisujemy ulepszenia dotyczące rozmiarów widżetów i większej elastyczności wprowadzone w Androidzie 12 (poziom API 31). Dowiesz się też, jak określić rozmiar widżetu .

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

Od Androida 12 (poziom API 31) możesz określać bardziej precyzyjne atrybuty rozmiaru i elastyczne układy, wykonując te czynności (opisane w kolejnych sekcjach):

  1. Określ dodatkowe ograniczenia dotyczące rozmiaru widżetu.

  2. Udostępnij układy elastyczne lub dokładne układy.

W poprzednich wersjach Androida można było uzyskać zakresy rozmiarów widżetu za pomocą OPTION_APPWIDGET_MIN_WIDTH, OPTION_APPWIDGET_MIN_HEIGHT, OPTION_APPWIDGET_MAX_WIDTH, i OPTION_APPWIDGET_MAX_HEIGHT dodatków, 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 udostępnianie układów elastycznych lub dokładnych.

Określanie dodatkowych ograniczeń dotyczących rozmiaru widżetu

Android 12 dodaje interfejsy API, które pozwalają zapewnić bardziej niezawodne określanie rozmiaru widżetu na różnych urządzeniach o różnych rozmiarach ekranu.

Oprócz dotychczasowych 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 komórkach siatki programu uruchamiającego. Jeśli są zdefiniowane, te atrybuty są używane zamiast minWidth lub minHeight.

  • maxResizeWidth i maxResizeHeight: określają 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>

Udostępnianie 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 jest prawidłowy w określonym zakresie rozmiarów. Jeśli nie jest to możliwe, możesz też udostępnić 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 lepszą kondycję systemu, ponieważ system nie musi wybudzać aplikacji za każdym razem, gdy wyświetla widżet w innym rozmiarze.

Ten przykład kodu 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>

Poprzedni fragment kodu oznacza:

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

Twój 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 przełączać układy.

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

Udostępnianie układów dokładnych

Jeśli niewielki zestaw układów elastycznych nie jest możliwy, możesz udostępnić różne układy dostosowane do rozmiarów, w których wyświetlany jest widżet. Zwykle są to 2 rozmiary w przypadku telefonów (tryb pionowy i poziomy) oraz 4 rozmiary w przypadku urządzeń składanych.

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

  1. Przeciąż AppWidgetProvider.onAppWidgetOptionsChanged(), który jest wywoływany, gdy zmieni się zestaw rozmiarów.

  2. Wywołaj AppWidgetManager.getAppWidgetOptions(), który zwraca Bundle zawierający rozmiary.

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

Ten przykład kodu pokazuje, jak udostępnić układy dokładne.

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 określać targetCellWidth i targetCellHeight w przypadku urządzeń z Androidem 12 lub nowszym albo minWidth i minHeight w przypadku wszystkich wersji Androida. Wskazują one minimalną ilość miejsca, jaką widżet zajmuje domyślnie. Gdy jednak użytkownicy dodają widżet do ekranu głównego, zwykle zajmuje on więcej niż minimalna szerokość i wysokość, które określisz.

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 tablety mogą oferować większą siatkę. Gdy dodasz widżet, zostanie on rozciągnięty, aby zajmować minimalną liczbę komórek w poziomie i pionie wymaganą 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 API 30) lub starszym.

Zarówno szerokość, jak 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. Użyj tej tabeli, aby w przybliżeniu oszacować minimalne wymiary widżetu na 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 trybie pionowym (dp) Dostępny rozmiar w trybie poziomym (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)

Użyj rozmiarów komórek w trybie pionowym, aby określić wartości atrybutów minWidth, minResizeWidth i maxResizeWidth. Podobnie użyj rozmiarów komórek w trybie poziomym, aby określić wartości atrybutów 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 chcesz, aby szerokość widżetu można było zmniejszyć do 1 komórki na Google Pixelu 4, musisz ustawić minResizeWidth na co najwyżej 56 dp, aby wartość atrybutu minResizeWidth była mniejsza niż 57 dp, ponieważ komórka ma co najmniej 57 dp szerokości w trybie pionowym. Podobnie, jeśli chcesz, aby wysokość widżetu można było zmniejszyć do 1 komórki na tym samym urządzeniu, musisz ustawić minResizeHeight na co najwyżej 50 dp, aby wartość atrybutu minResizeHeight była mniejsza niż 51 dp, ponieważ komórka ma co najmniej 51 dp wysokości w trybie poziomym.

Rozmiar każdego widżetu można zmieniać w zakresie od minResizeWidth/minResizeHeight do maxResizeWidth/maxResizeHeight, co oznacza, że musi się on dostosowywać do wszystkich zakresów rozmiarów między tymi wartościami.

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, jak określają atrybuty targetCellWidth i targetCellHeight, lub 180 x 110 dp, jak określają atrybuty minWidth i minHeight w przypadku urządzeń z Androidem 11 lub starszym. W tym drugim przypadku rozmiar w komórkach 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>

Jak określają poprzednie atrybuty, szerokość widżetu można zmieniać od 180 dp do 530 dp, a jego wysokość od 110 dp do 450 dp. Rozmiar widżetu można wtedy zmieniać od 3 x 2 do 5 x 2 komórek, o ile 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 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 (następne punkty odcięcia – 1). Podobnie 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 zmienia rozmiar widżetu, jego wygląd zmienia się, aby dostosować się do każdego rozmiaru w komórkach, jak pokazują te przykłady.

Przykład widżetu pogodowego w najmniejszym rozmiarze siatki 3x2. Interfejs pokazuje nazwę lokalizacji (Tokio), temperaturę (14°C) i symbol oznaczający częściowe zachmurzenie.
Rysunek 2. 3x2 R.layout.widget_weather_forecast_small.

Przykład widżetu pogodowego w rozmiarze „średnim” 4x2. Zmiana rozmiaru widżetu w ten sposób opiera się na wszystkich elementach interfejsu z poprzedniego rozmiaru widżetu i dodaje etykietę „Przeważnie pochmurno” oraz prognozę temperatur od 16:00 do 19:00.
Rysunek 3. 4x2 R.layout.widget_weather_forecast_medium.

Przykład widżetu pogodowego w rozmiarze 5x2 „średni”. Zmiana rozmiaru widżetu w ten sposób powoduje wyświetlenie tego samego interfejsu co w przypadku poprzedniego rozmiaru, z tym że jest on rozciągnięty o długość jednej komórki, aby zajmować więcej miejsca w poziomie.
Rysunek 4. 5x2 R.layout.widget_weather_forecast_medium.

Przykładowy widżet pogodowy w rozmiarze „duży” (5x3). Zmiana rozmiaru widżetu w ten sposób opiera się na wszystkich elementach interfejsu z poprzednich rozmiarów widżetu i dodaje widok w widżecie zawierający prognozę pogody na wtorek i środę. Symbole oznaczające słoneczną lub deszczową pogodę oraz najwyższe i najniższe temperatury w poszczególnych dniach.
Rysunek 5. 5x3 R.layout.widget_weather_forecast_large.

Przykład widżetu pogodowego w rozmiarze „duży” (5x4). 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 odpowiadające im symbole wskazujące rodzaj pogody, a także najwyższą i najniższą temperaturę każdego dnia).
Rysunek 6. 5x4 R.layout.widget_weather_forecast_large.