Das vergangene Google I / O 2019 brachte viele sensationelle Innovationen mit sich, von denen viele in den kommenden Jahren Auswirkungen auf die mobile Entwicklungsbranche haben werden. Es war nicht weniger interessant, den aufkommenden Trends zu folgen. Erstens gingen mechanische Steuertasten in die Geschichte ein, Smartphone-Bildschirme wurden größer und die Seitenrahmen wurden unauffälliger. Gesten ersetzten die Systemschaltflächen auf dem Bildschirm und ließen immer mehr Platz für den Konsum von Inhalten. Anwendungen werden auf der gesamten sichtbaren Oberfläche des Displays vom unteren bis zum oberen Rahmen angezeigt, ohne sich auf die bedingten Grenzen der Statusleiste und des Navigationsbereichs zu beschränken. Wir stehen kurz vor einer Edge-to-Edge-Ära.

Was ist Edge-to-Edge? Wörtlich verstanden bedeutet dies, dass Ihre Anwendung auf der gesamten sichtbaren Oberfläche des Displays vom unteren bis zum oberen Rahmen angezeigt werden sollte, ohne sich auf die Statusleiste und die unteren Navigationsschaltflächen zu beschränken.
Edge-to-Edge am Beispiel der Android-System-Shell.Wenn es um Android geht, ist eine einfache Idee bei weitem nicht immer einfach umzusetzen. In diesem Artikel werden wir darüber sprechen, wie Sie die Nutzung des gesamten verfügbaren Speicherplatzes auf einem beliebigen Gerät maximieren können, unabhängig von Hersteller, Version des Systems und den verschiedenen Einstellungen, die Hersteller von Geräten aus dem Reich der Mitte (und nicht nur) gerne den Benutzern gefallen. Der im Artikel vorgestellte Code wurde von uns persönlich auf mehr als 30 Geräten und von 100.000 Benutzern unserer Anwendungen auf 231 verschiedenen Geräten getestet.
Das Problem der Erstellung einer Edge-to-Edge-Schnittstelle ist an sich nicht neu und war lange vor der E / A 2019 relevant. Sicherlich wird sich jeder von Ihnen daran erinnern, wie Sie zuerst etwas aus der Kategorie
"Androide transparente Statusleiste" oder
"Android-Statusleistenverlauf" googeln.
" .
Die Hauptkriterien für die Anwendung, um dem Titel von Kante zu Kante zu entsprechen, sind:
- transparente Statusleiste;
- transparente Navigationsleiste.
Lesen Sie mehr darüber auf
material.io .
Die Deezer App macht sich keine Sorgen um die Edge-to-Edge-Konformität
Es ist wichtig zu beachten, dass wir nicht davon sprechen, sie vollständig zu entfernen, wie im "
Vollbildmodus ". Wir überlassen dem Benutzer die Möglichkeit, wichtige Systeminformationen anzuzeigen und die vertraute Navigation zu verwenden.
Eine ebenso wichtige Voraussetzung für eine Lösung ist die Skalierbarkeit und Erweiterbarkeit. Es gibt eine Reihe anderer:
- Verschieben Sie den Bildschirm korrekt über die Tastatur, ohne die Unterstützung für die adjustResize-Flags der Aktivität zu unterbrechen.
- Vermeiden Sie es, die Statusleiste und die Navigationsleiste auf den UI-Elementen der Anwendung zu überlagern, während Sie den entsprechenden Hintergrund darunter anzeigen.
- Arbeiten Sie auf allen Geräten mit aktuellen Android-Versionen und sehen Sie identisch aus.
Ein bisschen Theorie
Es kann unerwartet viel Zeit in Anspruch nehmen, eine Lösung für eine scheinbar einfache Aufgabe zu finden, die für den Projektmanager nicht leicht zu erklären ist. Und wenn die Qualitätssicherung immer noch das unglückliche Smartphone findet, auf dem Ihr Bildschirm nicht „nach den Kanonen“ aussieht ...
In unserem Projekt haben wir uns mehrmals geirrt. Nur einen Monat später, nach einer langen Reihe von Versuchen und Irrtümern, haben wir das Problem ein für alle Mal gelöst.
Zunächst müssen Sie verstehen, wie Android Systempanels zeichnet. Ab Android 5.0 wurde eine praktische API zum Arbeiten mit Systemeinzügen entlang der horizontalen Ränder des Bildschirms bereitgestellt. Sie heißen WindowInsets und sind im Bild unten rot gefärbt:
Außerdem haben Entwickler aus dem Android-Team Listener hinzugefügt, mit denen Sie Änderungen an diesen Einzügen abonnieren können, z. B. wenn die Tastatur angezeigt wird. Genau genommen sind WindowInsets die Ränder Ihrer Layoutdatei von den Rändern des Bildschirms. Wenn Sie die Größe Ihrer Aktivität ändern (Split-Screen-Modus, Tastaturaussehen), ändert sich auch das Einschub. Um Edge-to-Edge zu unterstützen, müssen wir daher sicherstellen, dass diese Einrückungen nicht vorhanden sind. Ein Bildschirm mit null WindowInsets sieht folgendermaßen aus:
Implementierung
In unserer Implementierung werden wir aktiv an Window und seinen Flags arbeiten.
Alle Beispiele werden in Kotlin geschrieben, aber Sie können sie problemlos in Java implementieren, indem Sie Dienstprogramme anstelle von Erweiterungsfunktionen verwenden.
Das erste, was Sie mit dem Root-Layout-Element tun müssen, ist, das Flag explizit zu setzen:
android:fitsSystemWindows="true"
Dies ist erforderlich, um sicherzustellen, dass die Stammansicht unter den Systemelementen gezeichnet wird, sowie um die korrekten Messungen von Inset zu erhalten, wenn Sie deren Änderung abonnieren.
Nun wenden wir uns dem Wichtigsten zu - wir entfernen die Ränder des Bildschirms! Dies muss jedoch sehr sorgfältig erfolgen. Und hier ist warum:
- Wenn Sie den unteren Einschub auf Null setzen, besteht die Gefahr, dass die Reaktion des Fensters auf das Erscheinungsbild der Tastatur verloren geht: Es gibt Dutzende von Tipps zu StackOverflow zum Zurücksetzen des oberen Einschubs, aber die unteren sind zart leise. Aus diesem Grund ist die Navigationsleiste nicht vollständig transparent. Beim Zurücksetzen des unteren Einschubs funktioniert das Flag "adjustResize" nicht mehr.
Lösung: Stellen Sie bei jedem Ändern des Einschubs fest, ob die untere Höhe der Tastatur darin enthalten ist, und setzen Sie sie nur anderweitig zurück. - Wenn Sie Inset zurücksetzen, werden die sichtbaren Teile der Ansicht unter der Statusleiste und der Navigationsleiste angezeigt. Nach dem Konzept des Materialdesigns (und des gesunden Menschenverstandes) sollten sich keine aktiven Elemente in Systembereichen befinden. Das heißt, in diesem Bereich sollten keine Schaltflächen, Felder zur Texteingabe, Kontrollkästchen usw. vorhanden sein.
Lösung: Wir fügen dem Listener einen Listener hinzu, sodass beim Ändern von WindowInsets die Systemeinzüge in die Aktivität übersetzt und intern darauf reagiert werden, indem die richtigen Auffüllungen und Ränder für die Ansicht festgelegt werden.

Dieses Verhalten sollte nicht zulässig sein (Symbolleiste wird in der Statusleiste gecrawlt).
Die Funktion
removeSystemInsets () sieht folgendermaßen aus:
fun removeSystemInsets(view: View, listener: OnSystemInsetsChangedListener) { ViewCompat.setOnApplyWindowInsetsListener(view) { _, insets -> val desiredBottomInset = calculateDesiredBottomInset( view, insets.systemWindowInsetTop, insets.systemWindowInsetBottom, listener ) ViewCompat.onApplyWindowInsets( view, insets.replaceSystemWindowInsets(0, 0, 0, desiredBottomInset) ) } }
Die Funktion
berechneDesiredBottomInset () berechnet den unteren Einschub mit oder ohne Tastatur, abhängig von der aktuellen Konfiguration des Geräts.
fun calculateDesiredBottomInset( view: View, topInset: Int, bottomInset: Int, listener: OnSystemInsetsChangedListener ): Int { val hasKeyboard = isKeyboardAppeared(view, bottomInset) val desiredBottomInset = if (hasKeyboard) bottomInset else 0 listener(topInset, if (hasKeyboard) 0 else bottomInset) return desiredBottomInset }
Verwenden Sie die Methode
isKeyboardAppeared () , um die Höhe der Tastatur zu überprüfen
. Wir vertrauten der Hypothese, dass die Tastatur nicht weniger als ein Viertel der Bildschirmhöhe einnehmen darf. Bei Bedarf können Sie die Überprüfungslogik nach Ihren Wünschen ändern.
private fun View.isKeyboardAppeared(bottomInset: Int) = bottomInset / resourdisplayMetrics.heightPixels.toDouble() > .25
Die Methode
removeSystemInsets () verwendet einen Listener. Eigentlich sind dies nur Typealien für einen Lambda-Ausdruck. Sein vollständiger Code:
typealias OnSystemBarsSizeChangedListener = (statusBarSize: Int, navigationBarSize: Int) -> Unit
Der nächste Schritt besteht darin, die Transparenz für die Systemleisten festzulegen:
window.statusBarColor = Color.TRANSPARENT window.navigationBarColor = Color.TRANSPARENT
Nachdem wir alle oben genannten Punkte zusammengestellt haben, erhalten wir die folgende Methode:
fun Activity.setWindowTransparency( listener: OnSystemInsetsChangedListener = { _, _ -> } ) { InsetUtil.removeSystemInsets(window.decorView, listener) window.navigationBarColor = Color.TRANSPARENT window.statusBarColor = Color.TRANSPARENT }
Um den Edge-to-Edge-Modus der gewünschten Aktivität zu aktivieren, müssen Sie nur die folgende Funktion in der
onCreate () -Methode
aufrufen :
setWindowTransparency { statusBarSize, navigationBarSize ->
So haben wir in weniger als 30 Codezeilen einen „Edge-to-Edge“ -Effekt erzielt, ohne gegen UX-Prinzipien zu verstoßen, ohne dem Benutzer die üblichen Systemsteuerungen zu entziehen. Eine solche Implementierung mag für jemanden einfach und trivial erscheinen, aber sie gewährleistet den zuverlässigen Betrieb Ihrer Anwendung auf allen Geräten.
Sie können den "Edge-to-Edge" -Effekt auf ungefähr hundert verschiedene Arten erzielen (die Anzahl
solcher Tipps in StackOverflow ist eine eindeutige Bestätigung dafür), aber viele von ihnen führen entweder zu falschem Verhalten bei verschiedenen Android-Versionen oder berücksichtigen keine Parameter wie die Notwendigkeit einer langen Anzeige Listen oder brechen Sie die Größe des Bildschirms, wenn Sie die Tastatur anzeigen.
Fliege in die Salbe
Die in diesem Artikel beschriebene Lösung ist für alle aktuellen Geräte geeignet. Tatsächlich bedeutet Geräte auf Android Lollipop (5.0) und höher. Für sie wird die obige Lösung perfekt funktionieren. Für ältere Android-Versionen benötigen Sie jedoch eine eigene Implementierung, da zu diesem Zeitpunkt noch nichts über WindowInsets bekannt war.
Die gute Nachricht ist, dass unter Android KitKat (4.4) die Transparenz von Systempanels weiterhin unterstützt wird. Aber ältere Versionen unterstützen solche Schönheit überhaupt nicht, man kann es nicht einmal versuchen.
Konzentrieren wir uns auf die Verschiebung von Insets in Android 4.4. Dies kann in der Methode
fitSystemWindows () erfolgen . Daher sollte das Hauptelement in Ihrem Layout ein Container mit einer überschriebenen fitSystemWindows-Methode sein, die genau die gleiche Implementierung enthält wie unser Listener im Beispiel für aktuelle Versionen von Android.
class KitkatTransparentSystemBarsFrame @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = -1 ) : FrameLayout(context, attrs, defStyleAttr), KitkatTransparentSystemBarsContainer { override var onSystemInsetsChangedListener: OnSystemInsetsChangedListener = { _, _ -> } override fun fitSystemWindows(insets: Rect?): Boolean { insets ?: return false val desiredBottomInset = InsetUtil.calculateDesiredBottomInset( this, insets.top, insets.bottom, onSystemInsetsChangedListener ) return super.fitSystemWindows(Rect(0, 0, 0, desiredBottomInset)) }
Auf Geräten mit Android 4.4 funktioniert nur teilweise Transparenz durch Setzen von durchscheinenden Flags:
window.addFlags( WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS or WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION )
Diese Flags machen die Systemleisten durchscheinend und fügen ihnen einen leichten Farbverlauf hinzu, der leider nicht entfernt werden kann. Mit dieser Bibliothek kann der Farbverlauf jedoch in eine durchscheinende Farbleiste umgewandelt werden:
https://github.com/jgilfelt/SystemBarTint . Sie hat uns in der Vergangenheit mehr als einmal gerettet. Die letzten Änderungen wurden vor 5 Jahren an der Bibliothek vorgenommen, sodass sie ihren Charme nur echten rückläufigen Städten offenbaren wird.
Der gesamte Markierungsprozess für Kitkat sieht folgendermaßen aus:
fun Activity.setWindowTransparencyKitkat( rootView: KitkatTransparentSystemBarsContainer, listener: OnSystemBarsSizeChangedListener = { _, _ -> } ) { rootView.onSystemBarsSizeChangedListener = listener window.addFlags( WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS or WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION ) }
Vor diesem Hintergrund schreiben wir eine universelle Methode, mit der Systemleisten transparent (oder zumindest durchscheinend) gemacht werden können, unabhängig davon, welche Anwendung auf einem Gerät mit welcher Android-Version ausgeführt wird:
when { Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP -> setWindowTransparency(::updateMargins) Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT -> setWindowTransparencyKitkat(root_container, ::updateMargins) else -> { } }
Unter dem Spoiler unten können Sie sehen, wie das im Artikel vorgestellte Beispiel auf einigen der problematischen Geräte aussieht:
ScreenshotsHuawei Honor 8, Android 7.0

Xiaomi Redmi Note 4, Android 6.1

HTC Desire Dual Sim, Android 4.4.2

Samsung J3, Android 7.0

Meizu M3s, Android 5.1

Asus Zenfone 3 Max, Android 6.0

Umi Rom, Android 5.0

Nexus 5X, Android 8.0

Samsung Galaxy S8, Android 9.0

Abschließend möchte ich sagen, dass die Lösung selbst einer scheinbar einfachen Aufgabe wie dem Festlegen von Transparenz für Elemente der Systembenutzeroberfläche Sie durch die Vielzahl von Fallstricken ziehen und letztendlich nicht zum gewünschten Ergebnis führen kann, sondern unangenehme Fehler verursacht. Gut, dass Sie diesen Artikel jetzt haben.
Eine vollständige Liste des Programms und ein Beispiel für die Arbeit finden Sie in unserem
Git-Repository .
Das Material ist inspiriert von dem Chris Banes-Bericht „Becoming a Master Window Fitter“.
Ich danke Surf Studio und Evgeny Saturov für die Hilfe bei der Vorbereitung des Materials.