Verschiedene Bildschirmgrößen unterstützen

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

Damit möglichst viele Bildschirmgrößen unterstützt werden, sollten Sie Ihre App-Layouts responsiv und adaptiv gestalten. Responsive/adaptive Layouts bieten eine optimierte Nutzererfahrung unabhängig von der Bildschirmgröße, sodass Ihre App für Smartphones, Tablets, faltbare Geräte, ChromeOS-Geräte, Hoch- und Querformat und anpassbare Konfigurationen wie der Mehrfenstermodus geeignet ist.

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 bestmöglich an unterschiedliche Bildschirmgrößen anpassen kann (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 spielt die physische Bildschirmgröße keine Rolle bei der Entscheidung, wie Inhalte angezeigt werden.

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 auch weniger spezielle Verarbeitungsschritte durchführen, um Plattformen wie ChromeOS und Formfaktoren wie Tablets und faltbare Geräte zu unterstützen.

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. Wenn der Status explizit übergeben wird, 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 lassen sich besser wiederverwenden, 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 Bereichen
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 eine zusammensetzbare Funktion ihr 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 dem Symbol, dem Titel und einer kurzen Beschreibung.

Wie bereits erwähnt, sollten wir es vermeiden, zu versuchen, die Größe des tatsächlichen Gerätebildschirms zu 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 Innenrand (z. B. für Einfügungen) platziert wird oder Komponenten wie Navigationsleisten oder App-Leisten vorhanden sind, kann der für die zusammensetzbare Funktion verfügbare Platz erheblich von dem für die App insgesamt verfügbaren Platz abweichen.

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

Wenn Sie ändern möchten, wo oder wie Inhalte angezeigt werden, können Sie eine Sammlung von Modifikatoren oder ein benutzerdefiniertes Layout verwenden, um das Layout responsiv zu machen. Dies kann so einfach sein, dass ein untergeordnetes Element den gesamten verfügbaren Bereich ausfüllen muss oder dass untergeordnete Elemente mit 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. Diese zusammensetzbare Funktion bietet Messbeschränkungen, mit denen Sie verschiedene zusammensetzbare Funktionen basierend auf dem verfügbaren Platz aufrufen können. Dies birgt jedoch gewisse Kosten, da BoxWithConstraints die Zusammensetzung bis zur Layoutphase verschiebt, wenn diese Einschränkungen bekannt sind. Dies führt dazu, dass während des Layouts mehr Arbeit ausgeführt wird.

@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 zur Verfügung gestellt werden können, um sie richtig zu rendern. Der zusammensetzbaren Funktion sollten genügend Daten zur Verfügung gestellt werden, damit sie immer alles hat, was sie für eine beliebige Größe anzeigen muss, 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 der description immer an den Card übergeben. Obwohl 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 Hochziehen von Informationen, die möglicherweise nicht für alle Größen verwendet werden, können wir den Zustand des Nutzers beibehalten, wenn sich die Layoutgröße ändert. Beispielsweise können wir das boolesche Flag showMore so hochziehen, dass der Status des Nutzers beibehalten wird, wenn Größenänderungen dazu führen, dass das Layout zwischen dem Ein- und Ausblenden 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 für eine optimale Nutzererfahrung auf Geräten mit großen Bildschirmen sorgen.
  • 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