Les dernières E / S de Google 2019 ont apporté de nombreuses innovations sensationnelles, dont beaucoup affecteront l'industrie du développement mobile dans les années à venir. Il n'était pas moins intéressant de suivre les tendances émergentes. Tout d'abord, les touches de commande mécaniques sont entrées dans l'histoire, les écrans des smartphones sont devenus plus grands et les cadres latéraux sont devenus plus discrets. Les gestes ont remplacé les boutons du système à l'écran, laissant de plus en plus d'espace pour la consommation de contenu. Les applications sont affichées sur toute la surface visible de l'écran, du bas vers le cadre supérieur, sans se limiter aux limites conditionnelles de la barre d'état et du panneau de navigation. Nous sommes à l'aube d'une ère de bord à bord.

Qu'est-ce que Edge-to-Edge? Littéralement compris, cela signifie que votre application doit être affichée sur toute la surface visible de l'écran, du bas vers le cadre supérieur, sans vous limiter à la barre d'état et aux boutons de navigation inférieurs.
Bord à bord sur l'exemple du shell du système Android.En ce qui concerne Android, une idée simple est loin d'être toujours facile à mettre en œuvre. Dans cet article, nous parlerons de la façon de maximiser l'utilisation de tout l'espace disponible sur l'écran de n'importe quel appareil, quel que soit le fabricant, la version du système et la variété des paramètres que les fabricants d'appareils de l'Empire du Milieu (et pas seulement) aiment faire plaisir aux utilisateurs. Le code présenté dans l'article a été testé sur plus de 30 appareils par nous personnellement, et sur 231 appareils différents par 100 000 utilisateurs de nos applications.
Le problème de la création d'une interface bord à bord n'est pas nouveau en soi et était pertinent bien avant les E / S 2019. Chacun d'entre vous se souviendra sûrement de la façon dont vous avez d'abord recherché sur Google quelque chose de la catégorie:
«barre d'état transparente Android» ou
«gradient de la barre d'état Android» " .
Les principaux critères pour que l'application corresponde au titre bord à bord sont:
- barre d'état transparente;
- Barre de navigation transparente.
En savoir plus sur eux sur
material.io .
L'application Deezer ne s'inquiète pas de la conformité Edge-to-Edge
Il est important de noter que nous ne parlons pas de les supprimer complètement, comme en "
mode plein écran ". Nous laissons à l'utilisateur la possibilité de consulter des informations importantes sur le système et d'utiliser une navigation familière.
Une exigence tout aussi importante pour une solution est l'évolutivité et l'extensibilité. Il en existe plusieurs autres:
- Déplacez correctement l'écran au-dessus du clavier sans interrompre le support des indicateurs d'activité adjustResize;
- Évitez de superposer la barre d'état et la barre de navigation sur les éléments d'interface utilisateur de l'application, tout en affichant l'arrière-plan correspondant sous eux;
- Fonctionne sur tous les appareils avec les versions actuelles d'Android et a l'air identique.
Un peu de théorie
De façon inattendue, cela peut prendre beaucoup de temps pour trouver une solution à une tâche aussi simple en apparence, qui ne sera pas facile à expliquer au chef de projet. Et quand QA trouve encore le smartphone malheureux, sur lequel votre écran ne ressemble pas "selon les canons" ...
Dans notre projet, nous nous sommes trompés plusieurs fois. Ce n'est qu'un mois plus tard, après avoir traversé une longue série d'essais et d'erreurs, que nous avons résolu le problème une fois pour toutes.
Tout d'abord, vous devez comprendre comment Android dessine les panneaux système. À partir d'Android 5.0, une API pratique a été fournie pour travailler avec les retraits du système le long des bords horizontaux de l'écran. Ils sont appelés WindowInsets, et dans l'image ci-dessous, ils sont colorés en rouge:
De plus, les développeurs de l'équipe Android ont ajouté des écouteurs qui vous permettent de vous abonner aux modifications de ces retraits, par exemple, lorsque le clavier apparaît. À strictement parler, les WindowInsets sont les marges de votre fichier de mise en page depuis les bords de l'écran. Lors du redimensionnement de votre activité (mode écran partagé, apparence du clavier), l'encart change également. Ainsi, pour prendre en charge bord à bord, nous devons nous assurer que ces retraits ne sont pas là. Un écran avec WindowInsets nul ressemblera à ceci:
Implémentation
Dans notre implémentation, nous opérons activement sur Window et ses drapeaux.
Tous les exemples seront écrits en Kotlin, mais vous pouvez facilement les implémenter en Java, en utilisant des utilitaires au lieu de fonctions d'extension.
La première chose à faire avec l'élément de disposition racine est de définir explicitement le drapeau:
android:fitsSystemWindows="true"
Cela est nécessaire pour garantir que la vue racine est dessinée sous les éléments du système, ainsi que pour les mesures correctes d'Inset lors de la souscription à leur modification.
Nous passons maintenant à la chose la plus importante - nous supprimons les bordures de l'écran! Cependant, cela doit être fait très soigneusement. Et voici pourquoi:
- En mettant à zéro l'encart inférieur, nous risquons de perdre la réaction de la fenêtre à l'apparence du clavier: il y a des dizaines de conseils sur StackOverflow pour réinitialiser l'encart supérieur, mais les inférieurs sont délicatement silencieux. Pour cette raison, la barre de navigation ne se révèle pas complètement transparente. Lors de la réinitialisation de l'encart inférieur, l'indicateur adjustResize cesse de fonctionner.
Solution: chaque fois que vous modifiez l'encart, déterminez si la hauteur inférieure du clavier y est contenue et réinitialisez-la uniquement dans le cas contraire. - Lorsque vous réinitialisez l'encart, les parties visibles de la vue s'affichent sous la barre d'état et la barre de navigation. Selon le concept de Material Design (et le bon sens), aucun élément actif ne doit être situé dans les zones du système. Autrement dit, dans cette zone, il ne devrait pas y avoir de boutons, de champs pour saisir du texte, des cases à cocher, etc.
Solution: nous allons ajouter un écouteur à l'écouteur de sorte que lorsque vous modifiez WindowInsets, traduisez les retraits du système en activité et répondez-y en interne en définissant les remplissages et les marges corrects pour la vue.

Ce comportement ne doit pas être autorisé (la barre d'outils explore la barre d'état).
La fonction
removeSystemInsets () ressemble à ceci:
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) ) } }
La fonction
CalculateDesiredBottomInset () calcule l' encart inférieur avec ou sans clavier, en fonction de la configuration actuelle de l'appareil.
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 }
Utilisez la méthode
isKeyboardAppeared () pour vérifier la hauteur du clavier
. Nous avons fait confiance à l'hypothèse que le clavier ne peut pas occuper moins d'un quart de la hauteur de l'écran. Si vous le souhaitez, vous pouvez modifier la logique de vérification à votre guise.
private fun View.isKeyboardAppeared(bottomInset: Int) = bottomInset / resourdisplayMetrics.heightPixels.toDouble() > .25
La méthode
removeSystemInsets () utilise un écouteur. En fait, ce ne sont que des typealias pour une expression lambda. Son code complet:
typealias OnSystemBarsSizeChangedListener = (statusBarSize: Int, navigationBarSize: Int) -> Unit
L'étape suivante consiste à définir la transparence des barres système:
window.statusBarColor = Color.TRANSPARENT window.navigationBarColor = Color.TRANSPARENT
Après avoir compilé tout ce qui précède, nous obtenons la méthode suivante:
fun Activity.setWindowTransparency( listener: OnSystemInsetsChangedListener = { _, _ -> } ) { InsetUtil.removeSystemInsets(window.decorView, listener) window.navigationBarColor = Color.TRANSPARENT window.statusBarColor = Color.TRANSPARENT }
Maintenant, pour activer le mode bord à bord de l'activité souhaitée, il vous suffit d'appeler la fonction suivante dans la méthode
onCreate () :
setWindowTransparency { statusBarSize, navigationBarSize ->
Ainsi, en moins de 30 lignes de code, nous avons obtenu un effet «bord à bord», sans violer aucun des principes UX et sans priver l'utilisateur des contrôles système habituels. Une telle implémentation peut sembler simple et triviale pour quelqu'un, mais c'est elle qui garantit le fonctionnement fiable de votre application sur tous les appareils.
Vous pouvez obtenir l'effet «bord à bord» d'une centaine de manières différentes (le nombre de
ces conseils sur StackOverflow en est une confirmation claire), mais beaucoup d'entre eux conduisent soit à un comportement incorrect sur différentes versions d'Android, soit ne prennent pas en compte des paramètres tels que la nécessité d'afficher longtemps listes ou briser le redimensionnement de l'écran lors de l'affichage du clavier.
Voler dans la pommade
La solution décrite dans cet article convient à tous les appareils actuels. Réel signifie les appareils sur Android Lollipop (5.0) et supérieur. Pour eux, la solution ci-dessus fonctionnera parfaitement. Mais pour les anciennes versions d'Android, vous aurez besoin de votre propre implémentation, car rien n'était connu sur WindowInsets à cette époque.
La bonne nouvelle est que sur Android KitKat (4.4), la transparence des panneaux système est toujours prise en charge. Mais les anciennes versions ne supportent pas du tout une telle beauté, vous ne pouvez même pas essayer.
Concentrons-nous sur le déplacement des encarts dans Android 4.4. Cela peut être fait dans la méthode
fitSystemWindows () . Ainsi, l'élément principal de votre mise en page doit être un conteneur avec une méthode fitSystemWindows remplacée contenant exactement la même implémentation que notre écouteur dans l'exemple pour les versions actuelles d'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)) }
Sur les appareils avec Android 4.4, seule une transparence partielle fonctionne en définissant des indicateurs translucides:
window.addFlags( WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS or WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION )
Ces drapeaux rendent les barres système translucides, en y ajoutant un léger dégradé, qui, malheureusement, ne peut pas être supprimé. Cependant, le dégradé peut être transformé en une barre de couleur translucide en utilisant cette bibliothèque:
https://github.com/jgilfelt/SystemBarTint . Elle nous a sauvés plus d'une fois dans le passé. Les dernières modifications ont été apportées à la bibliothèque il y a 5 ans, elle ne révèlera donc son charme qu'aux véritables villes rétrogrades.
L'ensemble du processus de signalement pour Kitkat ressemblera à ceci:
fun Activity.setWindowTransparencyKitkat( rootView: KitkatTransparentSystemBarsContainer, listener: OnSystemBarsSizeChangedListener = { _, _ -> } ) { rootView.onSystemBarsSizeChangedListener = listener window.addFlags( WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS or WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION ) }
Dans cet esprit, nous écrivons une méthode universelle qui peut rendre les barres système transparentes (ou au moins translucides), quelle que soit l'application exécutée sur un appareil avec quelle version d'Android:
when { Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP -> setWindowTransparency(::updateMargins) Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT -> setWindowTransparencyKitkat(root_container, ::updateMargins) else -> { } }
Sous le spoiler ci-dessous, vous pouvez voir à quoi ressemble l'exemple présenté dans l'article sur certains des appareils problématiques:
Captures d'écranHuawei 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 Rome, Android 5.0

Nexus 5X, Android 8.0

Samsung Galaxy S8, Android 9.0

En conclusion, je tiens à dire que la solution à une tâche aussi simple en apparence que la définition de la transparence pour les éléments de l'interface utilisateur du système peut vous entraîner le long de toute la variété des pièges et ne pas conduire finalement au résultat souhaité, mais provoquera plutôt des bugs désagréables. Heureusement que vous avez cet article maintenant.
Vous pouvez trouver une liste complète du programme et un échantillon de travail dans notre
référentiel git .
Le matériau est inspiré du rapport de Chris Banes «Devenir un maître installateur de fenêtres».
J'exprime ma gratitude à Surf studio et à Evgeny Saturov pour leur aide dans la préparation du matériel.