Skip to content

Edge-to-edge (SDK 36)#56

Open
Spriana wants to merge 6 commits into
FranckRJ:masterfrom
Spriana:android16sdk
Open

Edge-to-edge (SDK 36)#56
Spriana wants to merge 6 commits into
FranckRJ:masterfrom
Spriana:android16sdk

Conversation

@Spriana

@Spriana Spriana commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Presque pas de vérification au niveau du code/de la technique de ma part ; je n’ai pas cherché à comprendre si les commentaires laissés dans le code et le message du commit sont utiles. Ça marche, basta.

J’ai pas mal testé manuellement (navigation à gestes vs à trois boutons, Android 16 vs 14 (avant le edge-to-edge) vs 7.1.1, thèmes) et fait corrigé les erreurs trouvées, mais au vu de la montagne de changement il faudra prévenir les utilisateurs qu’il risque d’y avoir des bugs.

Graphiquement, le seul changement vraiment visible c’est que la couleur de la barre de statuts devient la même que la top bar (android:statusBarColor est ignoré), y compris sur les vieilles versions d’Android exprès pour s’aligner sur les nouvelles. La couleur spécifique à la barre de statuts pourrait être conservée mais ça va à l’encontre de Material 3 ; et je trouve que c’est mieux sans sur les écrans modernes avec une barre de statuts épaisse.

Ci-dessous les notes de mise en œuvre générées par Claude :


Cibler Android 16 (SDK 36) : edge-to-edge

Contexte : quand l'app cible le SDK 36, Android 16 impose le edge-to-edge et
l'opt-out windowOptOutEdgeToEdgeEnforcement qu'on utilisait est ignoré. Cette note
décrit comment RespawnIRC gère le edge-to-edge après la migration. Elle a été réécrite
après un test sur un vrai appareil Android 16, donc elle reflète ce qui a réellement
été livré — une première version décrivait une approche qui n'a pas survécu aux tests
sur appareil.

L'idée de base

Les widgets AndroidX historiques (CoordinatorLayout, DrawerLayout) essaient
d'« aider » avec les insets via android:fitsSystemWindows, et c'est précisément cette
aide qui casse tout sous edge-to-edge imposé :

  • le fitsSystemWindows du CoordinatorLayout consomme l'inset du haut, donc la
    toolbar ne peut pas peindre derrière la barre d'état → la barre d'état affiche le fond
    de fenêtre (clair) avec des icônes blanches = une barre d'état blanche / invisible ;
  • le DrawerLayout consomme les insets et réserve une marge en bas → une grosse bande
    grise morte en bas.

L'approche est donc : arrêter de compter sur fitsSystemWindows, le retirer des
layouts racines, et appliquer les insets nous-mêmes
— peindre la bande de barre d'état
via la toolbar, et réserver la place de la barre de navigation / du clavier seulement
là où c'est nécessaire.

Pourquoi l'opt-out a disparu

En ciblant le SDK 36, android:windowOptOutEdgeToEdgeEnforcement est déprécié et ignoré
sur Android 16 (encore respecté sur Android 15). Il est retiré de styles.xml.
android:statusBarColor est conservé et mis à ?attr/colorPrimary : il est ignoré
sous edge-to-edge (15+, où c'est la toolbar qui peint la bande), mais sous Android 15 (où
le système ne l'impose pas) c'est lui qui colore la barre d'état encore opaque — utiliser
colorPrimary (et
non l'ancien colorPrimaryDark) garde la barre de la même couleur que la barre du haut,
comme en 15+. L'ancien hack setStatusBarColor(TRANSPARENT) sur les écrans à menu latéral
a été retiré : il s'appuyait sur le scrim du DrawerLayout désormais neutralisé, donc sur
≤14 il laissait la barre afficher le fond de fenêtre gris.

Haut : la bande de barre d'état

Tous les écrans incluent @layout/toolbar et passent par
AbsToolbarActivity.initToolbar(), donc le haut est géré de façon centralisée :

  1. Retirer android:fitsSystemWindows="true" de chaque racine CoordinatorLayout
    (~15 layouts). Sans lui, l'inset atteint la toolbar au lieu d'être avalé par le
    Coordinator.
  2. Dans toolbar.xml, la hauteur passe à wrap_content +
    android:minHeight="?attr/actionBarSize" pour que le padding du haut agrandisse la
    barre au lieu d'écraser son contenu.
  3. Dans initToolbar(), ajouter l'inset du haut en padding sur la toolbar — son fond
    colorPrimary remplit alors la bande de barre d'état :
ViewCompat.setOnApplyWindowInsetsListener(myToolbar, (view, windowInsets) -> {
    Insets bars = windowInsets.getInsets(
            WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout());
    view.setPadding(bars.left, bars.top, bars.right, 0);
    return windowInsets;
});

Contraste des icônes (AbsThemedActivity) : icônes sombres uniquement pour le thème
clair à couleur primaire claire (signal toolbarTextColorIsInverted déjà existant),
blanches sinon :

WindowInsetsControllerCompat controller =
    WindowCompat.getInsetsController(getWindow(), getWindow().getDecorView());
controller.setAppearanceLightStatusBars(isLightTheme && toolbarTextColorIsInverted);

Écrans à menu latéral (showforum, selectforum)

Le DrawerLayout consomme les insets et réserve des marges, ce qui à la fois gêne
l'approche par toolbar (barre d'état) et crée une bande morte en bas. On le neutralise
pour que ces écrans se comportent comme les autres :

  • Retirer le fitsSystemWindows de la racine, et dans AbsNavigationViewActivity
    remplacer la gestion d'insets du DrawerLayout par un listener pass-through :
ViewCompat.setOnApplyWindowInsetsListener(layoutForDrawer, (v, insets) -> insets);

Les insets atteignent alors la toolbar (qui peint la bande) et le panneau du menu, et
plus rien ne réserve de marge en bas.

  • Sur le panneau (ScrimInsetsFrameLayout), mettre
    app:insetForeground="@android:color/transparent". Piège : retirer l'attribut
    ne suffit pas — le widget retombe sur un scrim sombre par défaut, qui apparaît sous
    forme de bandes grises en haut et en bas du menu ouvert. Il faut le mettre
    explicitement transparent.

  • La liste du menu elle-même prend l'inset de barre de navigation pour que son dernier
    élément (avec beaucoup de favoris) passe au-dessus de la barre et que le contenu
    défile dessous (barre transparente) comme les autres listes. Le ScrimInsetsFrameLayout
    consommant les insets, on pose le listener sur le panneau mais on padde la liste
    (clipToPadding=false) :

navigationMenuList.setClipToPadding(false);
ViewCompat.setOnApplyWindowInsetsListener((View) navigationMenuList.getParent(), (panel, insets) -> {
    int nav = insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom;
    navigationMenuList.setPadding(navigationMenuList.getPaddingLeft(), navigationMenuList.getPaddingTop(),
            navigationMenuList.getPaddingRight(), nav);
    return insets;
});

Padder le panneau laisserait la liste en retrait et son fond opaque ressemblerait à une
barre pleine ; padder la liste laisse le contenu défiler sous la barre transparente.

Bas : barre de navigation et clavier

Il n'y a aucun padding bas global (une première tentative de padding centralisé sur
android.R.id.content créait une grosse bande grise morte sur tous les écrans, pire en
navigation gestuelle). À la place :

  • Barre d'envoi des topics (ShowTopicActivity) : padding de
    max(navigationBars, ime) pour qu'elle reste au-dessus de la barre de navigation et
    monte au-dessus du clavier ; son fond déborde sous la barre de navigation
    (transparente) :
ViewCompat.setOnApplyWindowInsetsListener(messageSendLayout, (view, windowInsets) -> {
    int nav = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom;
    int ime = windowInsets.getInsets(WindowInsetsCompat.Type.ime()).bottom;
    view.setPadding(view.getPaddingLeft(), view.getPaddingTop(), view.getPaddingRight(),
                    Math.max(nav, ime));
    return windowInsets;
});
  • Listes défilantes : Utils.addBottomNavInsetPadding(view) met
    clipToPadding=false et ajoute l'inset de barre de navigation en padding bas — le
    contenu défile sous la barre, mais le dernier élément peut passer au-dessus (marche
    en navigation gestuelle et à 3 boutons) :
public static void addBottomNavInsetPadding(final View scrollView) {
    if (scrollView instanceof ViewGroup) {
        ((ViewGroup) scrollView).setClipToPadding(false);
    }
    final int basePadding = scrollView.getPaddingBottom();
    ViewCompat.setOnApplyWindowInsetsListener(scrollView, (v, windowInsets) -> {
        int navBottom = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom;
        v.setPadding(v.getPaddingLeft(), v.getPaddingTop(), v.getPaddingRight(),
                     basePadding + navBottom);
        return windowInsets;
    });
}

Appliqué à : la liste des topics d'un forum, le sélecteur de forum (liste + scrollview
de la liste de base), les résultats de recherche, la liste des ignorés, la liste des
comptes, la gestion de sondage, l'affichage d'un message, d'un sondage, des infos d'un
forum, et la liste des préférences (SettingsFragment via getListView()).

Pas appliqué à la liste des messages d'un topic : elle est au-dessus de la barre
d'envoi (layout_above), qui la sépare déjà de la barre de navigation — la padder
laisserait un trou.

Pièges constatés sur appareil

  • Le fitsSystemWindows des CoordinatorLayout / DrawerLayout doit être retiré (et le
    DrawerLayout neutralisé activement) — le garder donne la barre d'état blanche / la
    bande morte en bas.
  • Un padding bas global ressemble à une grosse bande morte, surtout en navigation
    gestuelle. Padder seulement les vraies barres du bas ; utiliser clipToPadding=false
    sur les listes.
  • Le ScrimInsetsFrameLayout a besoin d'un insetForeground mis à transparent, pas
    retiré.
  • Pour une liste défilante dans un conteneur qui consomme les insets (le
    ScrimInsetsFrameLayout du menu), padder la liste, pas le conteneur — padder le
    conteneur opaque ressemble à une barre pleine.
  • Sur Android ≤14 (pas d'edge-to-edge) la barre d'état est encore peinte par
    statusBarColor ; un setStatusBarColor(TRANSPARENT) oublié la laisse afficher le fond
    de fenêtre gris.
  • Vérifier sur un vrai appareil : chacune des erreurs ci-dessus est invisible dans le
    code et à la compilation — seul le lancement de l'app les révèle.

Matrice de tests

Tester sur un appareil/émulateur avec les 4 thèmes (Clair / Gris / Sombre / Noir) et les
deux modes de navigation (gestuelle + 3 boutons), en portrait et en paysage :

  • barre d'état colorée (pas blanche), contenu pas dessous ;
  • barre d'envoi des topics au-dessus du clavier et de la barre de navigation ;
  • dernier élément de liste au-dessus de la barre de navigation ;
  • menu latéral : bandeau sous la barre d'état, pas de bandes grises en haut/bas ; avec
    beaucoup de favoris, le dernier élément passe au-dessus de la barre et le contenu défile
    dessous ;
  • sous Android 15 (tester Android 14 dans les deux modes de navigation, plus le plancher
    minSdk) : barre d'état colorPrimary, pas grise. La navigation gestuelle (Android 10+)
    rend déjà la barre de navigation transparente, donc la gestion d'insets du bas s'applique
    aussi avant 15 — ce n'est pas une bascule « 15+ uniquement » nette.

Predictive back & passage au SDK (commits séparés)

  • Le predictive back est activé par défaut au target 36 et onBackPressed() n'est plus
    appelé. L'app conserve ses surcharges onBackPressed() pour l'instant via
    android:enableOnBackInvokedCallback="false" dans le manifeste (migration vers
    OnBackPressedDispatcher plus tard).
  • Le passage targetSdk / compileSdk 36 est un commit séparé ; c'est lui qui impose le
    edge-to-edge (géré ci-dessus) et active le predictive back (neutralisé ci-dessus).

Spriana and others added 6 commits June 26, 2026 08:15
Android 16 (targetSdk 36) impose le edge-to-edge et ignore l'opt-out windowOptOutEdgeToEdgeEnforcement, retiré ici. On gère donc les insets manuellement au lieu de compter sur fitsSystemWindows : padding du haut sur la toolbar (son fond colorPrimary peint la bande de barre d'état, icônes claires/sombres selon le thème), retrait de fitsSystemWindows des racines CoordinatorLayout et neutralisation de la gestion d'insets du DrawerLayout (sinon la toolbar ne peint pas la barre d'état et une marge grise apparaît en bas), padding du bas (barre de navigation + clavier) sur la barre d'envoi des topics, et padding du bas sur les listes pour garder le dernier élément au-dessus de la barre de navigation. Retrait aussi du scrim par défaut du ScrimInsetsFrameLayout du menu latéral. Vérifié sur Android 16.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ack=false

Au targetSdk 36 le predictive back est activé par défaut et onBackPressed n'est plus appelé. On le désactive temporairement pour conserver les surcharges onBackPressed existantes (drawer, brouillons, retour WebView, double-retour) jusqu'à leur migration vers OnBackPressedDispatcher.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le edge-to-edge (géré par le commit précédent) et le predictive back (neutralisé) sont prêts. Les autres changements du SDK 36 (orientation grand écran, pages 16 Ko, réseau local, intents) ne concernent pas cette app.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…a barre de navigation

Avec beaucoup de topics favoris la liste du menu latéral défile, et son dernier élément passait sous la barre de navigation. Le ScrimInsetsFrameLayout consommant les insets, on applique le padding bas (inset de barre de navigation) sur le panneau du menu (parent de la liste) plutôt que sur la liste. Vérifié sur Android 16 avec 11 favoris, navigation à 3 boutons.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…menu latéral

Au-dessus du menu latéral ouvert, la barre de navigation paraissait opaque (le fond plein du panneau remplissait la zone) alors qu'elle est transparente sur les autres écrans. On applique désormais clipToPadding + padding bas sur la liste du menu plutôt que sur le panneau (le ScrimInsetsFrameLayout consommant les insets, le listener reste posé sur le panneau) : le contenu défile sous la barre comme ailleurs et le dernier élément reste au-dessus d'elle. Vérifié sur Android 16, navigation à 3 boutons.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sur Android < 15 (pas d'edge-to-edge), la barre d'état des écrans à menu latéral était grise : le hack statusBarNeedToBeTransparent la rendait transparente, en s'appuyant sur le scrim du DrawerLayout désormais neutralisé. On retire ce hack et on passe statusBarColor de colorPrimaryDark à colorPrimary, pour que la barre d'état ait la même couleur que la barre du haut, comme le rendu edge-to-edge des versions 15+. Vérifié sur Android 7.1.1 (API 25).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant