fitsSystemWindows effect gone for fragments added via FragmentTransaction
OK, after several people pointing out that fitsSystemWindows
works differently, and it should not be used on every view down the hierarchy, I went on experimenting and removing the property from different views.
I got the expected state after removing fitsSystemWindows
from every node in activity.xml
=\
When you use <fragment>
, the layout returned in your Fragment's onCreateView
is directly attached in place of the <fragment>
tag (you'll never actually see a <fragment>
tag if you look at your View hierarchy.
Therefore in the <fragment>
case, you have
DrawerLayout
CoordinatorLayout
AppBarLayout
...
NavigationView
Similar to how cheesesquare works. This works because, as explained in this blog post, DrawerLayout
and CoordinatorLayout
both have different rules on how fitsSystemWindows
applies to them - they both use it to inset their child Views, but also call dispatchApplyWindowInsets() on each child, allowing them access to the fitsSystemWindows="true"
property.
This is a difference from the default behavior with layouts such as FrameLayout
where when you use fitsSystemWindows="true"
is consumes all insets, blindly applying padding without informing any child views (that's the 'depth first' part of the blog post).
So when you replace the <fragment>
tag with a FrameLayout
and FragmentTransactions, your view hierarchy becomes:
DrawerLayout
FrameLayout
CoordinatorLayout
AppBarLayout
...
NavigationView
as the Fragment's view is inserted into the FrameLayout
. That View doesn't know anything about passing fitsSystemWindows
to child views, so your CoordinatorLayout
never gets to see that flag or do its custom behavior.
Fixing the problem is actually fairly simple: replace your FrameLayout
with another CoordinatorLayout
. This ensures the fitsSystemWindows="true"
gets passed onto the newly inflated CoordinatorLayout
from the Fragment.
Alternate and equally valid solutions would be to make a custom subclass of FrameLayout
and override onApplyWindowInsets() to dispatch to each child (in your case just the one) or use the ViewCompat.setOnApplyWindowInsetsListener() method to intercept the call in code and dispatch from there (no subclass required). Less code is usually the easiest to maintain, so I wouldn't necessarily recommend going these routes over the CoordinatorLayout
solution unless you feel strongly about it.
My problem was similar to yours: I have a Bottom Bar Navigation which is replacing the content fragments. Now some of the fragments want to draw over the status bar (with CoordinatorLayout
, AppBarLayout
), others not (with ConstraintLayout
, Toolbar
).
ConstraintLayout
FrameLayout
[the ViewGroup of your choice]
BottomNavigationView
The suggestion of ianhanniballake to add another CoordinatorLayout
layer is not what I want, so I created a custom FrameLayout
which handles the insets (like he suggested), and after some time I came upon this solution which really is not much 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) {
}
});
}
}