Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to recover from SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION?

This problem actually arose in a more complex situation involving orientation changes and varying layouts for portrait and landscape but in it's minimal version the problem is this:

We would like to switch back and forth between a "normal" and "fullscreen" layout, i. e.:

  • one layout where the content only takes up the space that's left inside of navigation bar and status bar
  • one layout where the content takes up the whole screen and slides under both navigation and status bar

To switch from normal to fullscreen we are using:

public static final int EXPAND_FLAGS = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;

getWindow().getDecorView().setSystemUiVisibility(EXPAND_FLAGS);

And to switch back we tried:

public static final int SHRINK_FLAGS = View.SYSTEM_UI_FLAG_VISIBLE;

getWindow().getDecorView().setSystemUiVisibility(SHRINK_FLAGS);

So, from this

Layout after app start

we first "expand" to this:

Layout after "expand" click

but "shrinking" back does not work:

Layout after "expand" and "shrink"

So, it seems that while the Activity is only allowed to draw inside of the system UI it still "thinks" it should leave space for the system UI.

So my question is: What should SHRINK_FLAGS be in my code above or what should I do completely differently?

We are using an AppCompatActivity with a Theme.AppCompat.Light.DarkActionBar theme.

like image 949
david.mihola Avatar asked Sep 04 '15 09:09

david.mihola


1 Answers

Seems the root of your layout has android:fitsSystemWindows="true". For some reason, which I'm not aware of, fitsSystemWindows doesn't work well when changing system UI flags. Fortunately, there's a workaround for this problem.

First, set expand system UI flags unconditionally:

getWindow().getDecorView().setSystemUiVisibility(
        View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
);

Then remove android:fitsSystemWindows="true" from all ancestor views of the layout.

After that create a layout that will handle system insets changes and add or remove padding based on the value of its property. That's the most complicated part. You have to handle system insets differently depending on platform version. For versions below 20 (Kitkat Watch) you have to override View.fitSystemWindows() method:

@Override
protected boolean fitSystemWindows(final Rect insets) {
    if (mFit) {
        setPadding(insets.left, insets.top, insets.right, insets.bottom);
        // Do not propagate the system insets further.
        return true;
    } else {
        setPadding(0, 0, 0, 0);
        // Do not consume the insets and allow other views handle them.
        return false;
    }
}

For platform version greater or equal than 20 you need to override View.onApplyWindowInsets() method and to the same processing as in the previous method:

@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;
    }
}

And finally create a setter for the mFit field, which will notify the system that it must apply insets again:

mFit = fit;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
    requestApplyInsets();
} else {
    requestFitSystemWindows();
}

Now you can call setFit(false) to make your views layout behind the system UI and setFit(true) to make the layout fit system windows.

You can get the source code for this layout here.

like image 190
Michael Avatar answered Oct 21 '22 14:10

Michael