Verschiedene Bildschirmgrößen unterstützen

Durch die Unterstützung verschiedener Bildschirmgrößen können möglichst viele Nutzer auf Ihre App zugreifen.

Damit möglichst viele Bildschirmgrößen unterstützt werden, sollten Sie Ihre App-Layouts responsiv und adaptiv gestalten. Responsive/adaptive Layouts bieten unabhängig von der Bildschirmgröße eine optimierte Nutzererfahrung. So kann Ihre App auf Smartphones, Tablets, faltbaren Geräten, ChromeOS-Geräten, Hoch- und Querformaten sowie auf konfigurierbaren Bildschirmen wie dem Multifenstermodus verwendet werden.

Responsive/adaptive Layouts ändern sich je nach verfügbarem Displaybereich. Die Änderungen reichen von kleinen Layoutanpassungen, die den verfügbaren Platz optimal ausnutzen (responsives Design), bis hin zum vollständigen Ersetzen eines Layouts durch ein anderes, damit Ihre App sich am besten an unterschiedliche Bildschirmgrößen anpassen lässt (adaptives Design).

Als deklaratives UI-Toolkit eignet sich Jetpack Compose ideal zum Entwerfen und Implementieren von Layouts, die sich dynamisch ändern, um Inhalte auf verschiedenen Bildschirmgrößen unterschiedlich zu rendern.

Große Layoutänderungen für Composeable-Elemente auf Bildschirmebene explizit machen

Wenn Sie Compose zum Layouten einer gesamten Anwendung verwenden, belegen Composables auf App- und Bildschirmebene den gesamten Bereich, der Ihrer App zum Rendern zur Verfügung steht. Auf dieser Ebene Ihres Designs kann es sinnvoll sein, das Gesamtlayout eines Bildschirms zu ändern, um größere Bildschirme zu nutzen.

Verwenden Sie keine physischen Hardwarewerte, um Layoutentscheidungen zu treffen. Es kann verlockend sein, Entscheidungen anhand eines festen, materiellen Werts zu treffen (Ist das Gerät ein Tablet? Hat der physische Bildschirm ein bestimmtes Seitenverhältnis?), aber die Antworten auf diese Fragen sind möglicherweise nicht hilfreich, um den Bereich zu bestimmen, in dem Ihre Benutzeroberfläche funktionieren kann.

Ein Diagramm mit verschiedenen Geräteformfaktoren, darunter Smartphone, faltbares Gerät, Tablet und Laptop.
Abbildung 1. Smartphone-, faltbare-, Tablet- und Laptop-Formfaktoren

Auf Tablets wird eine App möglicherweise im Multifenstermodus ausgeführt, d. h., der Bildschirm wird mit einer anderen App geteilt. Unter ChromeOS wird eine App möglicherweise in einem Fenster ausgeführt, das sich anpassen lässt. Es kann sogar mehr als ein physisches Display geben, z. B. bei einem faltbaren Gerät. In all diesen Fällen ist die physische Bildschirmgröße für die Entscheidung, wie Inhalte angezeigt werden, nicht relevant.

Stattdessen sollten Sie Entscheidungen basierend auf dem tatsächlichen Teil des Bildschirms treffen, der Ihrer App zugewiesen ist, z. B. anhand der aktuellen Fenstermesswerte, die von der WindowManager-Bibliothek von Jetpack bereitgestellt werden. Eine Anleitung zur Verwendung von WindowManager in einer Compose-App finden Sie im Beispiel JetNews.

Wenn Sie diesen Ansatz verfolgen, wird Ihre App flexibler, da sie sich in allen oben genannten Szenarien gut verhalten wird. Wenn Sie Ihre Layouts an den verfügbaren Bildschirmbereich anpassen, müssen Sie weniger spezielle Funktionen für Plattformen wie ChromeOS und Formfaktoren wie Tablets und faltbare Geräte implementieren.

Nachdem Sie den für Ihre App verfügbaren Bereich ermittelt haben, ist es hilfreich, die Rohgröße in eine aussagekräftige Größenklasse umzuwandeln, wie unter Fenstergrößenklassen verwenden beschrieben. Die Größen werden in Standardgrößen-Buckets gruppiert. Diese Grenzwerte sollen Einfachheit mit der Flexibilität kombinieren, Ihre App für die meisten Anwendungsfälle zu optimieren. Diese Größenklassen beziehen sich auf das gesamte Fenster Ihrer App. Verwenden Sie diese Klassen also für Layoutentscheidungen, die sich auf das gesamte Bildschirmlayout auswirken. Sie können diese Größenklassen als Status weitergeben oder zusätzliche Logik ausführen, um abgeleiteten Status zu erstellen, der an verschachtelte Composeables übergeben wird.

@Composable
fun MyApp(
    windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
) {
    // Perform logic on the size class to decide whether to show the top app bar.
    val showTopAppBar = windowSizeClass.windowHeightSizeClass != WindowHeightSizeClass.COMPACT

    // MyScreen knows nothing about window sizes, and performs logic based on a Boolean flag.
    MyScreen(
        showTopAppBar = showTopAppBar,
        /* ... */
    )
}

Bei diesem mehrschichtigen Ansatz wird die Logik für die Bildschirmgröße an einem einzigen Ort festgelegt, anstatt sie an vielen Stellen in Ihrer App zu verteilen, die synchronisiert werden müssen. Dieser einzelne Standort erzeugt einen Status, der wie jeder andere App-Status explizit an andere Composeables übergeben werden kann. Durch das explizite Übergeben des Zustands werden einzelne Composeables vereinfacht, da es sich dabei nur um normale Composeable-Funktionen handelt, die neben anderen Daten auch die Größenklasse oder die angegebene Konfiguration berücksichtigen.

Flexible verschachtelte Composeables sind wiederverwendbar

Sie sind wiederverwendbarer, wenn sie an einer Vielzahl von Stellen platziert werden können. Wenn bei einem Composeable davon ausgegangen wird, dass es immer an einer bestimmten Stelle mit einer bestimmten Größe platziert wird, ist es schwieriger, es an anderer Stelle oder mit einer anderen verfügbaren Größe wiederzuverwenden. Das bedeutet auch, dass individuelle, wiederverwendbare Composeables nicht implizit von „globalen“ Größeninformationen abhängig sein sollten.

Betrachten Sie das folgende Beispiel: Angenommen, Sie haben ein verschachteltes Composeable, das ein Listendetaillayout implementiert, das einen oder zwei Bereiche nebeneinander anzeigen kann.

Screenshot einer App mit zwei nebeneinander liegenden Ansichten
Abbildung 2. Screenshot einer App mit einem typischen Listen-Detaillayout: 1 ist der Listenbereich, 2 der Detailbereich.

Wir möchten, dass diese Entscheidung Teil des Gesamtlayouts der App ist. Daher geben wir sie von einem Composeable auf Bildschirmebene weiter, wie oben gezeigt:

@Composable
fun AdaptivePane(
    showOnePane: Boolean,
    /* ... */
) {
    if (showOnePane) {
        OnePane(/* ... */)
    } else {
        TwoPane(/* ... */)
    }
}

Was ist, wenn wir stattdessen möchten, dass ein Composeable sein Layout unabhängig vom verfügbaren Platz ändert? Beispielsweise kann auf einer Karte zusätzlicher Text angezeigt werden, wenn der Platz dafür ausreicht. Wir möchten eine Logik basierend auf einer verfügbaren Größe ausführen, aber welche Größe genau?

Beispiele für zwei verschiedene Karten
Abbildung 3. Eine schmale Karte mit nur einem Symbol und einem Titel sowie eine breitere Karte mit Symbol, Titel und kurzer Beschreibung.

Wie bereits erwähnt, sollten wir die Größe des tatsächlichen Bildschirms des Geräts nicht verwenden. Dies ist nicht korrekt, wenn mehrere Bildschirme verwendet werden oder die App nicht im Vollbildmodus angezeigt wird.

Da das Composed-Element kein Composed-Element auf Bildschirmebene ist, sollten wir die Messwerte für das aktuelle Fenster auch nicht direkt verwenden, um die Wiederverwendbarkeit zu maximieren. Wenn die Komponente mit einem Abstand platziert wird (z. B. für Einzüge) oder es Komponenten wie Navigationsleisten oder App-Leisten gibt, kann sich der für das Composed-Element verfügbare Platz erheblich vom gesamten für die App verfügbaren Platz unterscheiden.

Daher sollten wir die Breite verwenden, die dem Composeable tatsächlich zugewiesen ist, um es zu rendern. Es gibt zwei Möglichkeiten, diese Breite zu erhalten:

Wenn Sie ändern möchten, wo oder wie Inhalte angezeigt werden, können Sie eine Reihe von Modifikatoren oder ein benutzerdefiniertes Layout verwenden, um das Layout responsiv zu gestalten. Das kann so einfach sein, dass ein Kind den gesamten verfügbaren Platz einnimmt, oder dass Kinder in mehreren Spalten angeordnet werden, wenn genügend Platz vorhanden ist.

Wenn Sie ändern möchten, was angezeigt wird, können Sie BoxWithConstraints als leistungsstärkere Alternative verwenden. Dieses Composeable bietet Größeeinschränkungen, mit denen Sie je nach verfügbarem Platz verschiedene Composeables aufrufen können. Dies hat jedoch einen gewissen Preis, da BoxWithConstraints die Komposition bis zur Layoutphase verschiebt, wenn diese Einschränkungen bekannt sind, was mehr Arbeit während des Layouts bedeutet.

@Composable
fun Card(/* ... */) {
    BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(/* ... */)
                Title(/* ... */)
            }
        } else {
            Row {
                Column {
                    Title(/* ... */)
                    Description(/* ... */)
                }
                Image(/* ... */)
            }
        }
    }
}

Alle Daten für verschiedene Größen verfügbar machen

Wenn Sie den zusätzlichen Bildschirmplatz nutzen, haben Sie auf einem großen Bildschirm möglicherweise Platz, um den Nutzern mehr Inhalte anzuzeigen als auf einem kleinen Bildschirm. Wenn Sie ein composable mit diesem Verhalten implementieren, kann es verlockend sein, effizient zu sein und Daten als Nebeneffekt der aktuellen Größe zu laden.

Dies verstößt jedoch gegen die Prinzipien des unidirektionalen Datenflusses, bei dem Daten hochgeladen und für Composables bereitgestellt werden können, um sie richtig zu rendern. Dem Composeable sollten genügend Daten zur Verfügung gestellt werden, damit es unabhängig von der Größe immer die erforderlichen Daten zur Anzeige hat, auch wenn ein Teil der Daten möglicherweise nicht immer verwendet wird.

@Composable
fun Card(
    imageUrl: String,
    title: String,
    description: String
) {
    BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(imageUrl)
                Title(title)
            }
        } else {
            Row {
                Column {
                    Title(title)
                    Description(description)
                }
                Image(imageUrl)
            }
        }
    }
}

Im Beispiel für Card wird immer description an Card übergeben. Auch wenn das description nur verwendet wird, wenn die Breite die Anzeige zulässt, ist es für Card immer erforderlich, unabhängig von der verfügbaren Breite.

Wenn Sie Daten immer übergeben, werden adaptive Layouts einfacher, da sie weniger zustandsabhängig sind. Außerdem werden beim Wechseln zwischen Größen keine Nebenwirkungen ausgelöst, die durch eine Fenstergrößenänderung, eine Änderung der Ausrichtung oder das Zusammen- und Aufklappen eines Geräts auftreten können.

Dieses Prinzip ermöglicht auch, den Status bei Layoutänderungen beizubehalten. Durch das Hochladen von Informationen, die nicht bei allen Größen verwendet werden, können wir den Zustand des Nutzers bei Änderungen der Layoutgröße beibehalten. Wir können beispielsweise ein showMore-Boolesche-Flag setzen, damit der Status des Nutzers beibehalten wird, wenn das Layout durch Größenänderungen zwischen Ausblenden und Einblenden der Beschreibung wechselt:

@Composable
fun Card(
    imageUrl: String,
    title: String,
    description: String
) {
    var showMore by remember { mutableStateOf(false) }

    BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(imageUrl)
                Title(title)
            }
        } else {
            Row {
                Column {
                    Title(title)
                    Description(
                        description = description,
                        showMore = showMore,
                        onShowMoreToggled = { newValue ->
                            showMore = newValue
                        }
                    )
                }
                Image(imageUrl)
            }
        }
    }
}

Weitere Informationen

Weitere Informationen zu benutzerdefinierten Layouts in Compose finden Sie in den folgenden zusätzlichen Ressourcen.

Beispiel-Apps

  • CanonicalLayouts ist ein Repository mit bewährten Designmustern, die eine optimale Nutzererfahrung auf Geräten mit großen Bildschirmen bieten.
  • JetNews zeigt, wie eine App gestaltet wird, deren Benutzeroberfläche sich an den verfügbaren Platz anpasst.
  • Antwort ist ein adaptives Sample für Mobilgeräte, Tablets und faltbare Geräte.
  • Jetzt bei Android ist eine App, die adaptive Layouts verwendet, um verschiedene Bildschirmgrößen zu unterstützen.

Videos