Reputation: 2340
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.
Upvotes: 22
Views: 17163
Reputation: 62189
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.
Upvotes: 15
Reputation: 1543
a) you can use CoordinatorLayout as your root view inside fragment
or
b) you can create custom linear layout what will call requestApplyInsets and use it as your root view inside fragment
class WindowInsetsLinearLayout : LinearLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun onAttachedToWindow() {
super.onAttachedToWindow()
ViewCompat.requestApplyInsets(this)
}
}
and then inside fragment you can catch applying insets
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
ViewCompat.setOnApplyWindowInsetsListener(root_layout) { _, insets ->
//appbar.setPadding(insets.systemWindowInsetLeft, insets.systemWindowInsetTop, 0, 0)
insets.consumeSystemWindowInsets()
}
}
Upvotes: 0
Reputation: 7837
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.
Upvotes: 1
Reputation: 4701
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
Upvotes: 3