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.

Gestalte deine App-Layouts so, dass sie responsiv und anpassungsfähig sind, um so viele Bildschirmgrößen wie möglich zu unterstützen. 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ügbarer Werbefläche. Die Änderungen reichen von kleinen Layoutanpassungen, die den Platz ausfüllen (responsives Design), bis hin zum vollständigen Ersetzen eines Layouts durch ein anderes, damit Ihre App am besten für verschiedene Anzeigegrößen geeignet ist (adaptives Design).

Als deklaratives UI-Toolkit eignet sich Jetpack Compose ideal zum Entwerfen und Implementieren von Layouts, die sich dynamisch ändern, um Inhalte für verschiedene Anzeigegrößen unterschiedlich zu rendern.

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

Wenn Sie mit „Compose“ das Layout einer gesamten Anwendung erstellen, nehmen zusammensetzbare Funktionen auf App- und Bildschirmebene den gesamten Bereich ein, der Ihrer App zum Rendern zur Verfügung steht. Auf dieser Designebene kann es sinnvoll sein, das Gesamtlayout eines Bildschirms zu ändern, um größere Bildschirme zu nutzen.

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

Ein Diagramm, das verschiedene Formfaktoren von Geräten zeigt, darunter Smartphone, faltbares Tablet, Tablet und Laptop.
Abbildung 1. Formfaktoren für Smartphone, faltbares Smartphone, Tablet und Laptop

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. Möglicherweise gibt es sogar mehr als einen Bildschirm, 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 auf Grundlage des tatsächlichen Teils des Bildschirms treffen, der Ihrer Anwendung zugewiesen ist, z. B. die aktuellen Fenstermesswerte, die von der Jetpack-Bibliothek WindowManager bereitgestellt werden. Informationen zur Verwendung von WindowManager in einer Compose-Anwendung finden Sie im JetNews-Beispiel.

Dieser Ansatz macht Ihre Anwendung flexibler, da sie sich in allen oben genannten Szenarien gut verhält. 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 relevanten Platz ermittelt haben, ist es hilfreich, die Rohgröße in eine sinnvolle Größenklasse zu konvertieren, wie unter Fenstergrößenklassen beschrieben. Dabei werden die Größen in Buckets mit Standardgröße zusammengefasst. Dies sind Haltepunkte, die darauf abzielen, ein Gleichgewicht zwischen Einfachheit und Flexibilität zu schaffen, um Ihre Anwendung für die meisten besonderen Fälle zu optimieren. Diese Größenklassen beziehen sich auf das Gesamtfenster Ihrer App. Sie sollten diese Klassen daher für Layoutentscheidungen verwenden, die sich auf Ihr gesamtes Bildschirmlayout auswirken. Sie können diese Größenklassen als Status übergeben oder zusätzliche Logik ausführen, um einen abgeleiteten Status zu erstellen, der an verschachtelte zusammensetzbare Funktionen übergeben wird.

@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 Speicherort beschränkt, anstatt sie an vielen Stellen in Ihrer App zu verteilen, die synchronisiert werden müssen. Dieser einzelne Speicherort erzeugt einen Status, der genau wie für jeden anderen Anwendungsstatus explizit an andere zusammensetzbare Funktionen übergeben werden kann. Die explizite Übergabe des Zustands vereinfacht einzelne zusammensetzbare Funktionen, da es sich dabei nur um normale zusammensetzbare Funktionen handelt, die neben anderen Daten die Größenklasse oder die angegebene Konfiguration übernehmen.

Flexibel verschachtelte, zusammensetzbare Funktionen sind wiederverwendbar

Zusammensetzbare Elemente sind besser wiederverwendbar, wenn sie an einer Vielzahl von Orten platziert werden können. Wenn eine zusammensetzbare Funktion davon ausgeht, dass sie immer an einem bestimmten Ort mit einer bestimmten Größe platziert wird, ist es schwieriger, sie an einem anderen Ort oder mit einem anderen verfügbaren Speicherplatz wiederzuverwenden. Das bedeutet auch, dass einzelne, wiederverwendbare zusammensetzbare Funktionen implizit nicht von "globalen" Größeninformationen abhängig sind.

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

Screenshot einer App mit zwei nebeneinanderliegenden Bereichen.
Abbildung 2. Screenshot einer App mit einem typischen Layout mit Listendetails: 1 ist der Listenbereich, 2 der Detailbereich.

Diese Entscheidung soll Teil des Gesamtlayouts der App sein. Deshalb geben wir die Entscheidung einer zusammensetzbaren Funktion auf Bildschirmebene weiter, wie wir es oben gesehen haben:

@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? Beispiel: eine Karte, auf der zusätzliche Details angezeigt werden sollen, wenn der Platz 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: Schmale Karte, die nur ein Symbol und einen Titel zeigt, und eine breitere Karte mit dem Symbol, dem Titel und der 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 genau für mehrere Bildschirme und auch nicht, wenn die App nicht im Vollbildmodus angezeigt wird.

Da es sich bei der zusammensetzbaren Funktion nicht um eine zusammensetzbare Funktion auf Bildschirmebene handelt, sollten wir auch die aktuellen Fenstermesswerte 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 zum Rendern die Breite verwenden, die die zusammensetzbare Funktion tatsächlich vorgegeben hat. 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 soll oder dass untergeordnete Elemente mit mehreren Spalten angeordnet werden, wenn genügend Platz vorhanden ist.

Wenn du ändern möchtest, was angezeigt wird, kannst du 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(/* ... */)
            }
        }
    }
}

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

Wenn Sie den zusätzlichen Bildschirmplatz nutzen, haben Sie auf einem großen Bildschirm möglicherweise Platz, um dem Nutzer mehr Inhalte anzuzeigen als auf einem kleinen Bildschirm. Bei der Implementierung einer zusammensetzbaren Funktion mit diesem Verhalten ist es verlockend, 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 hochgezogen und zusammensetzbaren Funktionen zur Verfügung gestellt werden können, um ein geeignetes Rendering zu erreichen. Der zusammensetzbaren Funktion sollte so viel Daten zur Verfügung gestellt werden, dass sie immer alles hat, was sie in beliebiger Größe anzeigen muss, selbst 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)
            }
        }
    }
}

Aufbauend auf dem Card-Beispiel wird description immer an 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.

Immer übergeben von Daten macht adaptive Layouts einfacher, da sie weniger zustandsorientiert werden. Außerdem wird vermieden, dass beim Wechsel zwischen Größen Nebeneffekte ausgelöst werden (z. B. aufgrund einer Größenänderung des Fensters, einer Änderung der Ausrichtung oder des Auf- und Zuklappens eines Geräts).

Dieses Prinzip ermöglicht es auch, den Zustand über Layoutänderungen hinweg 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. Wir können beispielsweise das boolesche Flag showMore hochziehen, damit der Status des Nutzers beibehalten wird, wenn Größenänderungen dazu führen, dass das Layout 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

  • Kanonische Layouts für große Bildschirme sind ein Repository bewährter Designmuster, die für eine optimale Nutzererfahrung auf Geräten mit großen Bildschirmen sorgen.
  • JetNews zeigt, wie man eine App entwirft, die ihre Benutzeroberfläche an den verfügbaren Platz anpasst
  • Reply ist ein adaptives Beispiel für die Unterstützung von Smartphones, Tablets und faltbaren Geräten.
  • Now in Android ist eine App, die adaptive Layouts verwendet, um verschiedene Bildschirmgrößen zu unterstützen.

Videos