Verschiedene Bildschirmgrößen unterstützen

Die Unterstützung für unterschiedliche Bildschirmgrößen ermöglicht den Zugriff auf Ihre App für die unterschiedlichste Geräte und größte Anzahl von Nutzern.

Damit möglichst viele Bildschirmgrößen unterstützt werden, sollten Sie Ihre App-Layouts so gestalten, dass sie responsiv und adaptiv sind. Responsive/adaptive Layouts bieten unabhängig von der Bildschirmgröße eine optimale Nutzererfahrung. Ihre App kann also auf Smartphones, Tablets, faltbaren Geräten, ChromeOS-Geräten, Hoch- und Querformaten sowie Konfigurationen mit anpassbarer Größe wie den Mehrfenstermodus verwendet werden.

Responsive/adaptive Layouts ändern sich je nach verfügbarer Displayfläche. Die Änderungen reichen von kleinen Layoutanpassungen, die Platz einnehmen (responsives Design), bis hin zum vollständigen Ersetzen eines Layouts durch ein anderes, damit deine App für verschiedene Bildschirmgrößen optimal geeignet ist (adaptives Design).

Als deklaratives UI-Toolkit ist Jetpack Compose ideal zum Entwerfen und Implementieren von Layouts, die sich dynamisch ändern, um Inhalte über verschiedene Bildschirmgrößen hinweg unterschiedlich zu rendern.

Große Layoutänderungen für zusammensetzbare Funktionen auf Bildschirmebene explizit machen

Wenn Sie mit der Funktion „Compose“ das Layout einer gesamten Anwendung erstellen, nehmen zusammensetzbare Funktionen auf App- und Bildschirmebene den gesamten Raum ein, den Ihre App für das Rendering zur Verfügung stellt. Auf dieser Designebene kann es sinnvoll sein, das Gesamtlayout eines Bildschirms zu ändern, um größere Bildschirme optimal zu nutzen.

Vermeiden Sie bei Layoutentscheidungen die Verwendung physischer Hardwarewerte. Es kann verlockend sein, Entscheidungen auf der Grundlage eines festen Werts zu treffen (Ist das Gerät ein Tablet? Hat der physische Bildschirm ein bestimmtes Seitenverhältnis? Die Antworten auf diese Fragen sind aber möglicherweise nicht hilfreich, um den Raum zu bestimmen, in dem Ihre UI arbeiten kann.

Ein Diagramm, das verschiedene Formfaktoren von Geräten zeigt, darunter Smartphone, faltbares Tablet, Tablet und Laptop.
Abbildung 1: Formfaktoren von Smartphones, faltbaren Tablets, Tablets und Laptops

Auf Tablets wird eine App möglicherweise im Mehrfenstermodus ausgeführt. Das bedeutet, dass die App den Bildschirm möglicherweise mit einer anderen App teilt. Unter ChromeOS befindet sich eine App möglicherweise in einem Fenster mit anpassbarer Größe. Es kann sogar mehr als einen physischen Bildschirm geben, z. B. bei einem faltbaren Gerät. In all diesen Fällen spielt die physische Bildschirmgröße keine Rolle für die Darstellung von Inhalten.

Stattdessen sollten Sie Entscheidungen basierend auf dem tatsächlichen Teil des Bildschirms treffen, der Ihrer Anwendung zugewiesen ist, z. B. die aktuellen Fenstermesswerte, die von der Jetpack-Bibliothek WindowManager bereitgestellt werden. Das JetNews-Beispiel zeigt, wie WindowManager in einer Anwendung zur Erstellung von Texten verwendet wird.

Mit diesem Ansatz wird Ihre Anwendung flexibler, da sie sich in allen oben genannten Szenarien gut verhält. Wenn du deine Layouts an den jeweiligen Bildschirmplatz anpasst, ist weniger spezielle Handhabung zur Unterstützung von Plattformen wie ChromeOS und Formfaktoren wie Tablets und faltbaren Geräten erforderlich.

Sobald Sie den relevanten verfügbaren Platz für Ihre App beobachtet haben, ist es hilfreich, die Rohgröße in eine aussagekräftige Größenklasse umzuwandeln (siehe Fenstergrößenklassen). Dabei werden Größen in Buckets in Standardgrößen zusammengefasst. Dabei handelt es sich um Haltepunkte, die ein Gleichgewicht zwischen Einfachheit und Flexibilität darstellen, Ihre Anwendung für die meisten besonderen Fälle zu optimieren. Diese Größenklassen beziehen sich auf das Gesamtfenster Ihrer App. Verwenden Sie sie also für Layoutentscheidungen, die sich auf das gesamte Bildschirmlayout auswirken. Sie können diese Größenklassen als Zustand weitergeben oder eine zusätzliche Logik ausführen, um einen abgeleiteten Status zu erstellen und ihn an verschachtelte zusammensetzbare Funktionen zu übergeben.

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@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 auf einen einzigen Ort beschränkt, anstatt sie in Ihrer Anwendung an vielen Stellen zu verteilen, die synchronisiert werden müssen. An diesem einzelnen Speicherort wird ein Status erzeugt, der genau wie jeder andere Anwendungsstatus explizit an andere zusammensetzbare Funktionen übergeben werden kann. Die explizite Übergabe des Zustands vereinfacht einzelne zusammensetzbare Funktionen, da es sich nur um normale zusammensetzbare Funktionen handelt, die die Größenklasse oder die angegebene Konfiguration zusammen mit anderen Daten annehmen.

Flexible verschachtelte zusammensetzbare Funktionen sind wiederverwendbar

Zusammensetzbare Funktionen sind wiederverwendbar, wenn sie an einer Vielzahl von Orten platziert werden können. Wenn bei einer zusammensetzbaren Funktion davon ausgegangen wird, dass sie sich immer an einem bestimmten Ort mit einer bestimmten Größe befindet, ist es schwieriger, sie an einem anderen Ort oder bei verfügbarem Speicherplatz wiederzuverwenden. Das bedeutet auch, dass einzelne, wiederverwendbare zusammensetzbare Funktionen nicht implizit von „globalen“ Größeninformationen abhängig sind.

Beispiel: Stellen Sie sich eine verschachtelte zusammensetzbare Funktion vor, die ein List-Detail-Layout implementiert, in dem ein oder zwei Bereiche nebeneinander angezeigt werden können.

Screenshot einer App mit zwei Bereichen nebeneinander.
Abbildung 2. Screenshot einer App mit einem typischen Listen-Detail-Layout – 1 ist der Listenbereich und 2 der Detailbereich.

Wir möchten, dass diese Entscheidung Teil des Gesamtlayouts der App ist. Daher geben wir die Entscheidung von einer zusammensetzbaren Funktion auf Bildschirmebene an, wie oben gezeigt:

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

Was ist, wenn eine zusammensetzbare Funktion ihr Layout basierend auf dem verfügbaren Platz unabhängig ändern soll? Zum Beispiel eine Karte, auf der zusätzliche Details angezeigt werden sollen, wenn der Platz ausreicht. Wir möchten einige Logik anhand einer verfügbaren Größe durchführen, aber um welche Größe geht es genau?

Beispiele für zwei verschiedene Karten.
Abbildung 3: Schmale Karte mit nur einem Symbol und Titel und eine breitere Karte mit Symbol, Titel und Kurzbeschreibung.

Wie oben gezeigt, sollten wir nicht versuchen, die tatsächliche Bildschirmgröße des Geräts zu verwenden. Die Daten sind nicht für mehrere Bildschirme genau und auch nicht genau, wenn die App nicht im Vollbildmodus ist.

Da die zusammensetzbare Funktion nicht auf Bildschirmebene zusammensetzbar ist, sollten die aktuellen Fenstermesswerte auch nicht direkt verwendet werden, um die Wiederverwendbarkeit zu maximieren. Wenn die Komponente mit einem Innenrand platziert wird (z. B. für Einfügungen) oder Komponenten wie Navigations- oder App-Leisten vorhanden sind, kann der für die zusammensetzbare Funktion verfügbare Platz erheblich vom insgesamt verfügbaren Platz der App abweichen.

Daher sollte die Breite verwendet werden, die der zusammensetzbaren Funktion tatsächlich zugewiesen wird, um sich selbst zu rendern. Sie haben 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. Dies kann so einfach sein, dass ein untergeordnetes Element den gesamten verfügbaren Platz ausfüllen oder die untergeordneten Elemente mit mehreren Spalten gestalten soll, wenn genug Platz vorhanden ist.

Wenn Sie ändern möchten, was angezeigt wird, können Sie BoxWithConstraints als leistungsstärkere Alternative verwenden. Diese zusammensetzbare Funktion bietet Messeinschränkungen, mit denen Sie verschiedene zusammensetzbare Funktionen je nach verfügbarem Platz aufrufen können. Dies ist jedoch mit Kosten verbunden, da die Zusammensetzung von BoxWithConstraints bis zur Layoutphase verschoben wird, wenn diese Einschränkungen bekannt sind. Dadurch wird während des Layouts mehr Arbeit ausgeführt.

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

Sicherstellen, dass alle Daten für verschiedene Größen verfügbar sind

Wenn Sie zusätzlichen Platz nutzen, haben Sie auf einem großen Bildschirm möglicherweise Platz, um dem Nutzer mehr Inhalte zu zeigen als auf einem kleinen Bildschirm. Wenn Sie eine zusammensetzbare Funktion mit diesem Verhalten implementieren, kann es verlockend sein, effizient zu arbeiten und Daten als Nebeneffekt der aktuellen Größe zu laden.

Dies verstößt jedoch gegen die Prinzipien des unidirektionalen Datenflusses, bei denen Daten hochgezogen und zusammensetzbaren Funktionen zum ordnungsgemäßen Rendern bereitgestellt werden können. Der zusammensetzbaren Funktion sollten genügend Daten zur Verfügung gestellt werden, damit sie unabhängig von ihrer Größe immer das hat, was sie anzeigen müssen, selbst wenn ein Teil der Daten 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)
            }
        }
    }
}

Basierend auf dem Card-Beispiel wird description immer an Card übergeben. Obwohl das description nur verwendet wird, wenn die Breite es zulässt, ist es für Card immer erforderlich, unabhängig von der verfügbaren Breite.

Durch das Übergeben von Daten werden adaptive Layouts einfacher, da sie weniger zustandsorientiert sind. Außerdem wird das Auslösen von Nebeneffekten beim Wechsel zwischen Größen vermieden (die aufgrund einer Änderung der Fenstergröße, Ausrichtungsänderung oder beim Falten und Aufklappen eines Geräts auftreten können).

Dieses Prinzip ermöglicht auch die Beibehaltung des Status bei Layoutänderungen. Durch das Hochlagern von Informationen, die nicht für alle Größen verwendet werden können, können wir den Zustand des Nutzers beibehalten, wenn sich die Layoutgröße ändert. Zum Beispiel kann das boolesche Flag showMore so platziert werden, dass der Status des Nutzers erhalten bleibt, wenn Größenänderungen dazu führen, dass das Layout zwischen „Ausblenden“ und „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

  • kanonische Layouts für große Bildschirme sind ein Repository mit bewährten Designmustern, die für eine optimale Nutzererfahrung auf Geräten mit großen Bildschirmen sorgen.
  • JetNews zeigt, wie eine App entworfen wird, bei der die Benutzeroberfläche so angepasst wird, dass der verfügbare Platz genutzt wird.
  • Antworten ist ein adaptives Beispiel zur Unterstützung von Smartphones, Tablets und faltbaren Geräten.
  • Now in Android ist eine App, die adaptive Layouts für unterschiedliche Bildschirmgrößen verwendet.

Videos