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

Wypróbuj tworzenie
Jetpack Compose to zalecany zestaw narzędzi interfejsu na Androida. 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 (API na poziomie 31). Znajdziesz w nim też informacje o tym, 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 (API na poziomie 31) możesz podawać bardziej precyzyjne atrybuty rozmiaru i elastyczne układy, wykonując te czynności (opisane w dalszych sekcjach):

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

  2. zapewniać elastyczne układy stron lub dokładne układy;

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

Określanie dodatkowych ograniczeń 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:

  • targetCellWidthtargetCellHeight: 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.

  • 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 rozmiaru.

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

zapewniać elastyczne układy,

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 odpowiedni dla określonego zakresu rozmiarów. Jeśli to nie jest możliwe, możesz 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 lepsze działanie systemu, ponieważ nie musi on wznawiać aplikacji za każdym razem, gdy wyświetla widżet w innym rozmiarze.

Poniższy przykład kodu pokazuje, jak podać listę układów.

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

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

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

Powyższy fragment kodu oznacza:

  • smallView obsługuje zakres 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).

Widżet musi obsługiwać zakres rozmiarów od minResizeWidth × minResizeHeight do maxResizeWidth × maxResizeHeight. W tym zakresie możesz określić punkt odcięcia, w którym nastąpi zmiana układu.

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

Podawanie dokładnych układów

Jeśli mały zestaw elastycznych układów nie jest możliwy, możesz zamiast tego podać różne układy dostosowane do rozmiarów, w których widżet jest wyświetlany. 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ąż metodę AppWidgetProvider.onAppWidgetOptionsChanged(), która jest wywoływana, gdy zmienia się zestaw rozmiarów.

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

  3. Otwórz klucz AppWidgetManager.OPTION_APPWIDGET_SIZESBundle.

Poniższy przykład kodu pokazuje, jak podać dokładne układy.

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

Określanie rozmiaru widżetu

Każdy widżet musi definiować targetCellWidthtargetCellHeight w przypadku urządzeń z Androidem 12 lub nowszym albo minWidthminHeight w przypadku wszystkich wersji Androida, wskazując minimalną ilość miejsca, jaką domyślnie zajmuje. 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ść.

Ekran główny Androida to siatka dostępnych miejsc, w których użytkownicy 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ę 5x4, a tablety mogą oferować większą siatkę. Gdy widżet zostanie dodany, zostanie rozciągnięty, aby zajmować minimalną liczbę komórek w pionie i poziomie wymaganą do spełnienia ograniczeń dotyczących jego targetCellWidthtargetCellHeight na urządzeniach z Androidem 12 lub nowszym albo ograniczeń minWidthminHeight na urządzeniach z Androidem 11 (API na poziomie 30) lub starszym.

Wymiary komórek i automatyczne marginesy różnią się w zależności od urządzenia. Skorzystaj z tej tabeli, aby oszacować minimalne wymiary widżetu na typowym urządzeniu z siatką 5x4, biorąc pod uwagę liczbę zajętych komórek siatki:

Liczba komórek (szerokość x wysokość) Dostępny rozmiar w trybie portretowym (dp) Dostępny rozmiar w trybie poziomym (dp)
1 x 1 57 x 102 dp 127x51dp
2x1 130 x 102 dp 269x51dp
3:1 203 x 102 dp 412x51dp
4:1 276 x 102 dp 554 x 51 dp
5:1 349 x 102 dp 697x51dp
5x2 349 x 220 dp 697x117dp
5x3 349x337dp 697 x 184 dp
5x4 349x455dp 697x250dp
...
n x m (73n - 16) x (118m - 16) (142n – 15) x (66m – 15)

Wartości atrybutów minWidth, minResizeWidth i maxResizeWidth powinny być zgodne z rozmiarami komórek w trybie pionowym. Podobnie używaj rozmiarów komórek w trybie poziomym, aby określać wartości atrybutów minHeight, minResizeHeight i maxResizeHeight.

Dzieje się tak, ponieważ 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 na Google Pixelu 4 można było zmniejszyć do 1 komórki, musisz ustawić wartość minResizeWidth na co najwyżej 56 dp, aby wartość atrybutu minResizeWidth była mniejsza niż 57 dp, ponieważ komórka w trybie pionowym ma co najmniej 57 dp szerokości. Podobnie, jeśli chcesz, aby wysokość widżetu w jednej komórce na tym samym urządzeniu można było zmieniać, musisz ustawić wartość minResizeHeight na co najwyżej 50 dp, aby wartość atrybutu minResizeHeight była mniejsza niż 51 dp – ponieważ jedna komórka ma co najmniej 51 dp wysokości w trybie poziomym.

Każdy widżet można zmieniać w zakresie rozmiarów określonym przez atrybuty minResizeWidth/minResizeHeightmaxResizeWidth/maxResizeHeight, co oznacza, że musi się on dostosowywać do dowolnego zakresu rozmiarów między nimi.

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

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

Oznacza to, że domyślny rozmiar widżetu to 3x2 komórki, zgodnie z atrybutami targetCellWidthtargetCellHeight, lub 180×110 dp, zgodnie z atrybutami minWidthminHeight 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 xmlns:android="http://schemas.android.com/apk/res/android"
    android:minResizeWidth="180dp"
    android:minResizeHeight="110dp"
    android:maxResizeWidth="530dp"
    android:maxResizeHeight="450dp">
</appwidget-provider>

Zgodnie z atrybutami podanymi powyżej szerokość widżetu można zmieniać w zakresie od 180 dp do 530 dp, a wysokość – od 110 dp do 450 dp. Widżet można następnie zmienić z 3x2 na 5x2 komórki, o ile spełnione są te warunki:

  • Urządzenie ma siatkę 5x4.
  • Mapowanie między liczbą komórek a dostępnym rozmiarem w dp jest zgodne z tabelą z szacunkami minimalnych wymiarów na tej stronie.
  • Widżet dostosuje się do tego zakresu rozmiarów.

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

Załóżmy, że widżet korzysta z 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 w przypadku rozmiarów od 180 dp (minResizeWidth) x 110 dp (minResizeHeight) do 269 x 279 dp (kolejne punkty odcięcia – 1). Podobnie symbol R.layout.widget_weather_forecast_medium jest używany w przypadku rozmiarów od 270 x 110 dp do 270 x 279 dp, a symbol R.layout.widget_weather_forecast_large – w przypadku rozmiarów od 270 x 280 dp do 530 dp (maxResizeWidth) x 450 dp (maxResizeHeight).

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

Przykład widżetu pogodowego w najmniejszym rozmiarze siatki 3x2. Interfejs wyświetla 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ę „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.