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 – ob verschiedene Gerätebildschirme oder verschiedene App-Fenster im Multifenstermodus – sollten Sie Ihre App-Layouts responsiv und anpassungsfähig 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, im Hoch- und Querformat sowie auf Bildschirmen mit variabler Größe wie im Splitscreen-Modus und im Desktopfenster 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 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 Composeables auf Inhaltsebene explizit machen

Composeable auf App-Ebene und Inhaltsebene belegen den gesamten für Ihre App verfügbaren Displaybereich. Bei diesen Arten von Composeable kann es sinnvoll sein, das Gesamtlayout Ihrer App auf großen Displays zu ändern.

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 das Display ein bestimmtes Seitenverhältnis?), aber die Antworten auf diese Fragen sind möglicherweise nicht hilfreich, um den für Ihre Benutzeroberfläche verfügbaren Platz zu bestimmen.

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. Im Fenstermodus für Desktop-Computer oder unter ChromeOS wird eine App möglicherweise in einem Fenster ausgeführt, das sich ändern 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 Displays treffen, der Ihrer App zugewiesen ist. Dieser wird durch die aktuellen Fenstermesswerte beschrieben, die von der WindowManager-Bibliothek von Jetpack bereitgestellt werden. Ein Beispiel für die Verwendung von WindowManager in einer Compose-App finden Sie im Beispiel JetNews.

Wenn Sie Ihre Layouts an den verfügbaren Displaybereich anpassen, müssen Sie auch weniger spezielle Anpassungen vornehmen, um Plattformen wie ChromeOS und Formfaktoren wie Tablets und faltbare Geräte zu unterstützen.

Nachdem Sie die Messwerte für den für Ihre App verfügbaren Bereich ermittelt haben, wandeln Sie die Rohgröße in eine Fenstergrößenklasse um, wie unter Fenstergrößenklassen verwenden beschrieben. Fenstergrößenklassen sind Breakpoints, die die Einfachheit der App-Logik mit der Flexibilität kombinieren, Ihre App für die meisten Displaygrößen zu optimieren. Fenstergrößenklassen beziehen sich auf das gesamte Fenster Ihrer App. Verwenden Sie die Klassen also für Layoutentscheidungen, die sich auf das gesamte App-Layout auswirken. Sie können Klassen für die Fenstergröße als Status weitergeben oder zusätzliche Logik ausführen, um abgeleiteten Status zu erstellen, der an verschachtelte Kompositionen ü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 einem mehrschichtigen Ansatz wird die Logik für die Displaygröße an einem einzigen Ort festgelegt, anstatt sie an vielen Stellen in Ihrer App zu verteilen, die synchronisiert werden müssen. Ein einzelner 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 Composables vereinfacht, da die Composables die Fenstergrößenklasse oder die angegebene Konfiguration zusammen mit anderen Daten übernehmen.

Flexible verschachtelte Composeables sind wiederverwendbar

Sie sind wiederverwendbarer, wenn sie an einer Vielzahl von Stellen platziert werden können. Wenn ein Composeable an einer bestimmten Stelle mit einer bestimmten Größe platziert werden muss, ist es unwahrscheinlich, dass es in anderen Kontexten wiederverwendet werden kann. Das bedeutet auch, dass einzelne, wiederverwendbare Composeables nicht implizit von globalen Informationen zur Bildschirmgröße abhängen sollten.

Stellen Sie sich ein verschachteltes Composeable vor, das ein Listendetaillayout implementiert, das entweder einen einzelnen Bereich oder zwei Bereiche nebeneinander anzeigen kann:

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

Die Entscheidung für die Listendetails sollte Teil des Gesamtlayouts der App sein. Daher wird die Entscheidung von einem Composeable auf Inhaltsebene übergeben:

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

Was ist, wenn Sie stattdessen möchten, dass ein Composeable sein Layout unabhängig vom verfügbaren Displaybereich ändert, z. B. eine Karte, die bei ausreichend Platz zusätzliche Details anzeigt? Sie möchten eine Logik basierend auf einer verfügbaren Bildschirmgröße ausführen, aber welche Größe genau?

Abbildung 3. Eine schmale Karte mit nur einem Symbol und einem Titel sowie eine breitere Karte mit Symbol, Titel und kurzer Beschreibung.

Verwenden Sie nicht die Größe des tatsächlichen Displays des Geräts. Das ist nicht genau für verschiedene Bildschirmtypen und auch nicht, wenn die App nicht im Vollbildmodus angezeigt wird.

Da es sich bei dem Composeable nicht um ein Composeable auf Inhaltsebene handelt, sollten Sie die Messwerte für den aktuellen Zeitraum nicht direkt verwenden. Wenn die Komponente mit einem Abstand platziert wird (z. B. mit Einzügen) oder die App Komponenten wie Navigationsleisten oder App-Leisten enthält, kann sich der für das Composed-Element verfügbare Anzeigebereich erheblich vom gesamten für die App verfügbaren Bereich unterscheiden.

Verwenden Sie die Breite, die dem Composeable tatsächlich zum Rendern zugewiesen ist. Sie haben zwei Möglichkeiten, diese Breite zu erreichen:

  • Wenn Sie ändern möchten, wo oder wie Inhalte angezeigt werden, verwenden Sie eine Reihe von Modifikatoren oder ein benutzerdefiniertes Layout, um das Layout responsiv zu gestalten. Das kann so einfach sein wie das Ausfüllen des gesamten verfügbaren Bereichs durch ein untergeordnetes Element oder das Layout von untergeordneten Elementen in mehreren Spalten, wenn genügend Platz vorhanden ist.

  • Wenn Sie festlegen möchten, was angezeigt wird, verwenden Sie BoxWithConstraints als leistungsstärkere Alternative. BoxWithConstraints bietet Größeeinschränkungen, mit denen Sie je nach verfügbarem Displaybereich verschiedene Composeables aufrufen können. Das 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(/* ... */)
            }
        }
    }
}

Sorgen Sie dafür, dass alle Daten für verschiedene Bildschirmgrößen verfügbar sind.

Wenn Sie ein Composed-Element implementieren, das zusätzlichen Displaybereich nutzt, sind Sie möglicherweise versucht, effizient zu sein und Daten als Nebeneffekt der aktuellen Displaygröße zu laden.

Dies verstößt jedoch gegen das Prinzip des unidirektionalen Datenflusses, bei dem Daten hochgeladen und für Composables zur Verfügung gestellt werden können, damit sie richtig gerendert werden. Für das Composeable sollten genügend Daten bereitgestellt werden, damit es für jede Bildschirmgröße immer genügend Inhalt hat, auch wenn ein Teil des Inhalts 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 Card übergeben. Auch wenn das description nur verwendet wird, wenn die Breite die Anzeige zulässt, ist das description für Card immer erforderlich, unabhängig von der verfügbaren Breite.

Wenn Sie immer genügend Inhalte übergeben, werden adaptive Layouts einfacher, da sie weniger zustandsabhängig sind. Außerdem werden so Nebenwirkungen beim Wechseln zwischen Displaygrößen vermieden, die durch eine Fenstergrößenänderung, eine Ausrichtungsänderung oder das Auf- und Zuklappen eines Geräts auftreten können.

Dieses Prinzip ermöglicht auch, den Status bei Layoutänderungen beizubehalten. Wenn Sie Informationen, die nicht bei allen Bildschirmgrößen verwendet werden, hochladen, können Sie den App-Status bei Änderungen der Layoutgröße beibehalten. Sie können beispielsweise ein showMore-Boolesche-Flag setzen, damit der App-Status beibehalten wird, wenn das Layout durch die Größenänderung des Displays zwischen Ausblenden und Einblenden von Inhalten 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 adaptiven Layouts in Compose finden Sie in den folgenden Ressourcen:

Beispiel-Apps

  • CanonicalLayouts ist ein Repository mit bewährten Designmustern, die eine optimale Nutzererfahrung auf großen Displays bieten.
  • JetNews zeigt, wie Sie eine App entwerfen, deren Benutzeroberfläche sich an den verfügbaren Displaybereich anpasst.
  • Antwort ist ein adaptives Beispiel 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