web-dev-qa-db-fra.com

l'effet fitsSystemWindows a disparu pour les fragments ajoutés via FragmentTransaction

J'ai une activité avec un tiroir de navigation et un fragment à fond perdu (avec une image dans le haut qui doit apparaître derrière la barre système translucide de Lollipop). Bien que j'aie eu une solution provisoire où le fragment a été gonflé simplement en ayant la balise <fragment> dans le code XML d'Activity, cela avait l'air d'aller.

Ensuite, j'ai dû remplacer <fragment> par <FrameLayout> et effectuer des transactions de fragment. À présent, le fragment n'apparaît plus derrière la barre système, bien que fitsSystemWindows soit défini sur true dans toute la hiérarchie requise.

Je pense qu’il pourrait y avoir une différence entre la façon dont <fragment> est gonflé dans la présentation d’Activity et par lui-même. J'ai cherché sur Google et trouvé des solutions pour KitKat, mais aucune de celles-ci n'a fonctionné pour moi (Lollipop).

activity.xml

<Android.support.v4.widget.DrawerLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
                                        xmlns:app="http://schemas.Android.com/apk/res-auto"
                                        Android:id="@+id/drawer_layout"
                                        Android:layout_height="match_parent"
                                        Android:layout_width="match_parent"
                                        Android:fitsSystemWindows="true">

    <FrameLayout
            Android:id="@+id/fragment_Host"
            Android:layout_width="match_parent"
            Android:layout_height="match_parent"
            Android:fitsSystemWindows="true">

    </FrameLayout>

    <Android.support.design.widget.NavigationView
            Android:id="@+id/nav_view"
            Android:layout_height="match_parent"
            Android:layout_width="wrap_content"
            Android:layout_gravity="start"
            Android:fitsSystemWindows="true"/>

</Android.support.v4.widget.DrawerLayout>

fragment.xml

<Android.support.design.widget.CoordinatorLayout
        xmlns:Android="http://schemas.Android.com/apk/res/Android"
        xmlns:app="http://schemas.Android.com/apk/res-auto"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        Android:fitsSystemWindows="true">

    <Android.support.design.widget.AppBarLayout
            Android:layout_width="match_parent"
            Android:layout_height="224dp"
            Android:fitsSystemWindows="true"
            Android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
...

Cela fonctionnait quand activity.xml était ainsi:

<Android.support.v4.widget.DrawerLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
                                        xmlns:app="http://schemas.Android.com/apk/res-auto"
                                        Android:id="@+id/drawer_layout"
                                        Android:layout_height="match_parent"
                                        Android:layout_width="match_parent"
                                        Android:fitsSystemWindows="true">

    <fragment xmlns:Android="http://schemas.Android.com/apk/res/Android"
              xmlns:tools="http://schemas.Android.com/tools"
              Android:id="@+id/fragment"
              Android:name="com.actinarium.random.ui.home.HomeCardsFragment"
              tools:layout="@layout/fragment_home"
              Android:layout_width="match_parent"
              Android:layout_height="match_parent"/>

    <Android.support.design.widget.NavigationView
            Android:id="@+id/nav_view"
            Android:layout_height="match_parent"
            Android:layout_width="wrap_content"
            Android:layout_gravity="start"
            Android:fitsSystemWindows="true"/>

</Android.support.v4.widget.DrawerLayout>
40
Actine

Lorsque vous utilisez <fragment>, la disposition renvoyée dans la onCreateView de votre fragment est directement attachée à la place de la balise <fragment> (vous ne verrez jamais réellement de balise <fragment> si vous regardez votre hiérarchie View.

Par conséquent, dans le cas <fragment>, vous avez

DrawerLayout
  CoordinatorLayout
    AppBarLayout
    ...
  NavigationView

Semblable à la façon dont cheesesquare fonctionne. Cela fonctionne parce que, comme expliqué dans cet article de blog , DrawerLayout et CoordinatorLayout ont tous deux des règles différentes sur la façon dont fitsSystemWindows s'applique à eux - ils l'utilisent tous deux pour insérer des vues enfant, mais aussi call dispatchApplyWindowInsets ( ) sur chaque enfant, leur permettant d'accéder à la propriété fitsSystemWindows="true".

Cela diffère du comportement par défaut avec des mises en page telles que FrameLayout où, lorsque vous utilisez fitsSystemWindows="true", toutes les insertions sont consommées, en appliquant aveuglément un remplissage sans informer aucune vue enfant (c'est la partie "profondeur en premier" de l'article de blog).

Ainsi, lorsque vous remplacez la balise <fragment> par FrameLayout et FragmentTransactions, votre hiérarchie de vues devient:

DrawerLayout
  FrameLayout
    CoordinatorLayout
      AppBarLayout
      ...
  NavigationView

comme la vue du fragment est insérée dans la FrameLayout. Cette vue ne sait rien sur le passage de fitsSystemWindows à des vues enfants. Votre CoordinatorLayout ne verra jamais cet indicateur ni ne se comportera de manière personnalisée.

Résoudre le problème est en fait assez simple: remplacez votre FrameLayout par un autre CoordinatorLayout. Cela garantit que le fitsSystemWindows="true" est transmis au CoordinatorLayout nouvellement gonflé à partir du fragment.

Une solution alternative tout aussi valable serait de créer une sous-classe personnalisée de FrameLayout et de remplacer onApplyWindowInsets () à distribuer à chaque enfant (dans votre cas, celui-ci) ou d’utiliser la méthode ViewCompat.setOnApplyWindowInsetsListener () intercepter l'appel en code et l'envoyer à partir de là (aucune sous-classe requise). Moins de code est généralement le plus facile à gérer, je ne recommanderais donc pas nécessairement ces itinéraires plutôt que la solution CoordinatorLayout à moins que cela ne vous dérange vraiment.

67
ianhanniballake

Mon problème était similaire au vôtre: j'ai un Bottom Bar Navigation qui remplace les fragments de contenu. Maintenant, certains fragments veulent dessiner sur la barre d'état (avec CoordinatorLayout, AppBarLayout), d'autres pas (avec ConstraintLayout, Toolbar).

ConstraintLayout
  FrameLayout
    [the ViewGroup of your choice]
  BottomNavigationView

La suggestion de ianhanniballake d’ajouter un autre calque CoordinatorLayout n’est pas ce que je veux, j’ai donc créé une coutume FrameLayout qui gère les incrustations (comme il l’a suggéré), et après un certain temps, je suis tombé sur cette solution qui n’a vraiment pas beaucoup de code. :

activity_main.xml

<Android.support.constraint.ConstraintLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
    xmlns:app="http://schemas.Android.com/apk/res-auto"
    Android:id="@+id/content"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent">

    <com.example.app.WindowInsetsFrameLayout
        Android:id="@+id/fragment_container"
        Android:layout_width="0dp"
        Android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/bottom_navigation"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <BottomNavigationView
        Android:id="@+id/bottom_navigation"
        Android:layout_width="0dp"
        Android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

</Android.support.constraint.ConstraintLayout>

WindowInsetsFrameLayout.Java

/**
 * FrameLayout which takes care of applying the window insets to child views.
 */
public class WindowInsetsFrameLayout extends FrameLayout {

    public WindowInsetsFrameLayout(Context context) {
        this(context, null);
    }

    public WindowInsetsFrameLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public WindowInsetsFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        // Look for replaced fragments and apply the insets again.
        setOnHierarchyChangeListener(new OnHierarchyChangeListener() {
            @Override
            public void onChildViewAdded(View parent, View child) {
                requestApplyInsets();
            }

            @Override
            public void onChildViewRemoved(View parent, View child) {

            }
        });
    }

}
11
Marius

OK, après que plusieurs personnes aient fait remarquer que fitsSystemWindows fonctionnait différemment et qu'il ne devait pas être utilisé dans toutes les vues de la hiérarchie, j'ai continué à expérimenter et à supprimer la propriété de différentes vues.

J'ai obtenu l'état attendu après avoir supprimé fitsSystemWindows de chaque noeud dans activity.xml = \

3
Actine

Une autre approche écrite en Kotlin,

Le problème:

La FrameLayout que vous utilisez ne propage pas fitsSystemWindows="true" à ses fils:

<FrameLayout
    Android:id="@+id/fragment_Host"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:fitsSystemWindows="true" />

Une solution:

Étendez FrameLayout class et override à la fonction onApplyWindowInsets() pour propager les encarts de fenêtre aux fragments attachés:

@TargetApi(Build.VERSION_CODES.Lollipop)
class BetterFrameLayout : FrameLayout {

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)

    override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets {
        childCount.let {
            // propagates window insets to children's
            for (index in 0 until it) {
                getChildAt(index).dispatchApplyWindowInsets(windowInsets)
            }
        }
        return windowInsets
    }
}

Utilisez cette présentation comme un conteneur de fragments au lieu de la variable standard FrameLayout:

<com.foo.bar.BetterFrameLayout
    Android:id="@+id/fragment_Host"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:fitsSystemWindows="true" />

Supplémentaire:

Si vous voulez en savoir plus sur cet article de blog Chris Banes/ Devenir un installateur de fenêtres maître .

1
Ryan Amaral

J'ai créé l'année dernière pour résoudre ce problème: https://Gist.github.com/cbeyls/ab6903e103475bd4d51b

Edit: assurez-vous de bien comprendre ce qui convient à SystemWindows. Lorsque vous le définissez sur une vue, cela signifie essentiellement: "placez cette vue et tous ses enfants sous la barre d'état et au-dessus de la barre de navigation". Cela n'a aucun sens de définir cet attribut sur le conteneur du haut.

1
BladeCoder