I have SingleFramgnetActivity
whose purpose is only to hold and replace fragments inside it.
layout looks like this:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
tools:context=".SingleFragmentActivity"
>
<include layout="@layout/toolbar"/>
<FrameLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
I'm replacing the Fragments
inside the FrameLayout. When I set the fitsSystemWindows
to true on the Fragment
layout, it is not responding. Actually it is working only when Activity
is created, but once I replace the Fragment
inside the FrameLayout
, the fitsSystemWindows
parameter is ignored and the layout is below the status bar and navigation bar.
I found some solution with custom FrameLayout which is using deprecated methods, but for some reason it is not working for me (same result as with normal FrameLayout) and I also do not like the idea to use deprecated methods.
Your FrameLayout
is not aware of window inset sizes, because it's parent - LinearLayout
hasn't dispatched it any. As a workaround, you can subclass LinearLayout
and pass insets to children on your own:
@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
int childCount = getChildCount();
for (int index = 0; index < childCount; index++)
getChildAt(index).dispatchApplyWindowInsets(insets); // let children know about WindowInsets
return insets;
}
You can have a look to my this answer, which will explain detailed how this works, and also how to use ViewCompat.setOnApplyWindowInsetsListener
API.
You could also build a custom WindowInsetsFrameLayout
and use a OnHierarchyChangedListener
to request applying the insets again:
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) {
}
});
}
Check out this detailed answer: https://stackoverflow.com/a/47349880/3979479
I think the problem revolves around onApplyWindowInsets
getting called before the fragment view hierarchy gets attached. An effective solution is to get the following override on a view somewhere in the view hierarchy of the fragment.
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
// force window insets to get re-applied if we're being attached by a fragment.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
requestApplyInsets();
} else {
//noinspection deprecation
requestFitSystemWindows();
}
}
A complete solution (if you don't have to use CoordinatorLayout
) follows. Make sure fitSystemWindows=true
does not appear ANYWHERE in views higher in the heirarchy. Maybe not anywhere else. I suspect (but am not sure) that consumeSystemWindowInsets
eats the insets for views that are further on in the layout order of the view tree.
package com.twoplay.xcontrols;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.view.WindowInsets;
import android.widget.FrameLayout;
public class FitSystemWindowsLayout extends FrameLayout {
private boolean mFit = true;
public FitSystemWindowsLayout(final Context context) {
super(context);
init();
}
public FitSystemWindowsLayout(final Context context, final AttributeSet attrs) {
super(context, attrs);
init();
}
public FitSystemWindowsLayout(final Context context, final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setFitsSystemWindows(true);
}
public boolean isFit() {
return mFit;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
requestApplyInsets();
} else {
//noinspection deprecation
requestFitSystemWindows();
}
}
public void setFit(final boolean fit) {
if (mFit == fit) {
return;
}
mFit = fit;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
requestApplyInsets();
} else {
//noinspection deprecation
requestFitSystemWindows();
}
}
@SuppressWarnings("deprecation")
@Override
protected boolean fitSystemWindows(final Rect insets) {
if (mFit) {
setPadding(
insets.left,
insets.top,
insets.right,
insets.bottom
);
return true;
} else {
setPadding(0, 0, 0, 0);
return false;
}
}
@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
@Override
public WindowInsets onApplyWindowInsets(final WindowInsets insets) {
if (mFit) {
setPadding(
insets.getSystemWindowInsetLeft(),
insets.getSystemWindowInsetTop(),
insets.getSystemWindowInsetRight(),
insets.getSystemWindowInsetBottom()
);
return insets.consumeSystemWindowInsets();
} else {
setPadding(0, 0, 0, 0);
return insets;
}
}
}
Suspicion, not fact: that only one view in the entire hierarchy gets a chance to eat the window insets, UNLESS you have CoordinatorLayout
in the hierarchy, which allows more than one direct child to have FitSystemWindow=true
. If you do have a CoordinatorLayout, your mileage may vary.
This entire feature in Android seems to be an unholy mess.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With