Android 4.4 — Translucent status/navigation bars — fitsSystemWindows/clipToPadding don't work through fragment transactions

StackOverflow https://stackoverflow.com/questions/20822418

Question

When using the translucent status and navigation bars from the new Android 4.4 KitKat APIs, setting fitsSystemWindows="true" and clipToPadding="false" to a ListView works initially. fitsSystemWindows="true" keeps the list under the action bar and above the navigation bar, clipToPadding="false" allows the list to scroll under the transparent navigation bar and makes the last item in the list scroll up just far enough to pass the navigation bar.

However, when you replace the content with another Fragment through a FragmentTransaction the effect of fitsSystemWindows goes away and the fragment goes under the action bar and navigation bar.

I have a codebase of demo source code here along with a downloadable APK as an example: https://github.com/afollestad/kitkat-transparency-demo. To see what I'm talking about, open the demo app from a device running KitKat, tap an item in the list (which will open another activity), and tap an item in the new activity that opens. The fragment that replaces the content goes under the action bar and clipToPadding doesn't work correctly (the navigation bar covers the last item in the list when you scroll all the way down).

Any ideas? Any clarification needed? I posted the before and after screenshots of my personal app being developed for my employer.

One Two

Was it helpful?

Solution 2

I solved the issue by using the library I use the set the color of my translucent status bar.

The SystemBarConfig class of SystemBarTint (as seen here https://github.com/jgilfelt/SystemBarTint#systembarconfig) lets you get insets which I set as the padding to the list in every fragment, along with the use of clipToPadding="false" on the list.

I have details of what I've done on this post: http://mindofaandroiddev.wordpress.com/2013/12/28/making-the-status-bar-and-navigation-bar-transparent-with-a-listview-on-android-4-4-kitkat/

OTHER TIPS

I struggled with the same problem yesterday. After thinking a lot, I found an elegant solution to this problem.

First, I saw the method requestFitSystemWindows() on ViewParent and I tried to call it in the fragment's onActivityCreated() (after the Fragment is attached to the view hierarchy) but sadly it had no effect. I would like to see a concrete example of how to use that method.

Then I found a neat workaround: I created a custom FitsSystemWindowsFrameLayout that I use as a fragment container in my layouts, as a drop-in replacement for a classic FrameLayout. What it does is memorizing the window insets when fitSystemWindows() is called by the system, then it propagates the call again to its child layout (the fragment layout) as soon as the fragment is added/attached.

Here's the full code:

public class FitsSystemWindowsFrameLayout extends FrameLayout {

    private Rect windowInsets = new Rect();
    private Rect tempInsets = new Rect();

    public FitsSystemWindowsFrameLayout(Context context) {
        super(context);
    }

    public FitsSystemWindowsFrameLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public FitsSystemWindowsFrameLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected boolean fitSystemWindows(Rect insets) {
        windowInsets.set(insets);
        super.fitSystemWindows(insets);
        return false;
    }

    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        super.addView(child, index, params);
        tempInsets.set(windowInsets);
        super.fitSystemWindows(tempInsets);
    }
}

I think this is much simpler and more robust than hacks that try to determine the UI elements sizes by accessing hidden system properties which may vary over time and then manually apply padding to the elements.

Okay, so this is incredibly weird. I just recently ran into this same issue except mine involves soft keyboard. It initially works but if I add fragment transaction, the android:fitsSystemWindows="true" no longer works. I tried all the solution here, none of them worked for me.

Here is my problem: enter image description here

Instead of re-sizing my view, it pushes up my view and that is the problem.

However, I was lucky and accidentally stumbled into an answer that worked for me!

So here it is:

First of all, my app theme is: Theme.AppCompat.Light.NoActionBar (if that is relevant, maybe it is, android is weird).

Maurycy pointed something very interesting here, so I wanted to test what he said was true or not. What he said was true in my case as well...UNLESS you add this attribute to your activity in the android manifest of your app:

enter image description here

Once you add:

android:windowSoftInputMode="adjustResize" 

to your activity, android:fitsSystemWindows="true" is no longer ignored after the fragment transaction!

However, I prefer you calling android:fitsSystemWindows="true" NOT on the root layout of your Fragment. One of the biggest places where this problem will occur is where if you have EditText or a ListView. If you are stuck in this predicament like I did, set android:fitsSystemWindows="true" in the child of the root layout like this:

enter image description here

YES, this solution works on all Lollipop and pre-lollipop devices.

And here is the proof: enter image description here

It re-sizes instead of pushing the layout upwards. So hopefully, I have helped someone who is on the same boat as me.

Thank you all very much!

A heads up for some people running into this problem.

A key piece of information with fitSystemWindows method which does a lot of the work:

This function's traversal down the hierarchy is depth-first. The same content insets object is propagated down the hierarchy, so any changes made to it will be seen by all following views (including potentially ones above in the hierarchy since this is a depth-first traversal). The first view that returns true will abort the entire traversal.

So if you have any other fragments with content views which have fitsSystemWindows set to true the flag will potentially be ignored. I would consider making your fragment container contain the fitsSystemWindows flag if possible. Otherwise manually add padding.

I've been struggling quite a bit with this as well. I've seen all the responses here. Unfortunately none of them was fixing my problem 100% of the time. The SystemBarConfig is not working always since it fails to detect the bar on some devices. I gave a look at the source code and found where the insets are stored inside the window.

        Rect insets = new Rect();
        Window window = getActivity().getWindow();
        try {
            Class clazz = Class.forName("com.android.internal.policy.impl.PhoneWindow");
            Field field = clazz.getDeclaredField("mDecor");
            field.setAccessible(true);
            Object decorView = field.get(window);
            Field insetsField = decorView.getClass().getDeclaredField("mFrameOffsets");
            insetsField.setAccessible(true);
            insets = (Rect) insetsField.get(decorView);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

This is how to get them. Apparently in Android L there'll be a nice method to get those insets but in the meantime this might be a good solution.

I encountered the same problem. When I replace Fragment. The 'fitsSystemWindows' doesn't work.

I fixed by code add to your fragment

@Override
public void onViewCreated(final View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    AndroidUtil.runOnUIThread(new Runnable() {
        @Override
        public void run() {
            ((ViewGroup) getView().getParent()).setFitsSystemWindows(true);
        }
    });
}

Combined with @BladeCoder answer i've created FittedFrameLayout class which does two things:

  • it doesn't add padding for itself
  • it scan through all views inside its container and add padding for them, but stops on the lowest layer (if fitssystemwindows flag is found it won't scan child deeper, but still on same depth or below).

    public class FittedFrameLayout extends FrameLayout {
        private Rect insets = new Rect();
    
        public FittedFrameLayout(Context context) {
            super(context);
        }
    
        public FittedFrameLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public FittedFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        @TargetApi(Build.VERSION_CODES.LOLLIPOP)
        public FittedFrameLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
        }
    
        protected void setChildPadding(View view, Rect insets){
            if(!(view instanceof ViewGroup))
                return;
    
            ViewGroup parent = (ViewGroup) view;
            if (parent instanceof FittedFrameLayout)
                ((FittedFrameLayout)parent).fitSystemWindows(insets);
            else{
                if( ViewCompat.getFitsSystemWindows(parent))
                    parent.setPadding(insets.left,insets.top,insets.right,insets.bottom);
                else{
                    for (int i = 0, z = parent.getChildCount(); i < z; i++)
                        setChildPadding(parent.getChildAt(i), insets);
                }
            }
        }
    
        @Override
        protected boolean fitSystemWindows(Rect insets) {
            this.insets = insets;
            for (int i = 0, z = getChildCount(); i < z; i++)
                setChildPadding(getChildAt(i), insets);
    
            return true;
        }
    
        @Override
        public void addView(View child, int index, ViewGroup.LayoutParams params) {
            super.addView(child, index, params);
            setChildPadding(child, insets);
        }
    }
    

I have resolve this question in 4.4

if(test){
    Log.d(TAG, "fit true ");
    relativeLayout.setFitsSystemWindows(true);
    relativeLayout.requestFitSystemWindows();
    getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}else {
    Log.d(TAG, "fit false");
    relativeLayout.setFitsSystemWindows(false);
    relativeLayout.requestFitSystemWindows();
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top