Immersive mode navigation becomes sticky after volume press or minimise-restore
Asked Answered
C

3

30
public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        this.getWindow().getDecorView().setSystemUiVisibility(getSystemUiFlags());
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    private static int getSystemUiFlags() {
            return View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                    | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
    }

}

After first start

After first start

After volume buttons pressed or after recent apps pressed twice

enter image description here

I saw QuickPic app doesn't have this bug. I wonder how they omitted it.

Clabo answered 16/1, 2014 at 14:40 Comment(0)
R
61

The following code works for me:

public void updateUI() {
    final View decorView = getWindow().getDecorView();
    decorView.setOnSystemUiVisibilityChangeListener (new View.OnSystemUiVisibilityChangeListener() {
        @Override
        public void onSystemUiVisibilityChange(int visibility) {
            if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
                decorView.setSystemUiVisibility(
                        View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                            | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                            | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                            | View.SYSTEM_UI_FLAG_FULLSCREEN
                            | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
                }
            }
        });
}

And called the listener on onCreate and onResume methods:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    updateUI();
}

@Override
public void onResume() {
    super.onResume();
    updateUI();
}
Retrieval answered 22/1, 2014 at 8:44 Comment(8)
Does this fix still have the effect of the black bar during volume change?Jonis
@Peter Brooks I have tried this, and even though it fixes the immersive mode, the navigation bar is indeed black for the second it is visible.Dex
it doesn't work with me in case of appearing keyboardMoriah
its does still show for a second, but at lease my content is not moved so that helped me a lot :)Pinchbeck
Exquisite. Thank you gentDollarfish
Works perfectly on Android N. Thanks!Hellenhellene
Relevant documentation: developer.android.com/training/system-ui/visibility.htmlNatka
setOnSystemUiVisibilityChangeListener needs fitsSystemWindows set to false in order to workRivard
B
29

My solution is to set the UI-Visibility flags in three places:

  1. When getting the focus
  2. In onResume
  3. In the OnSystemUiVisibilityChangeListener listener

The third solved my problem. The others might not be needed, but I left them. This is what is looks like:

  private void setupMainWindowDisplayMode() {
    View decorView = setSystemUiVisilityMode();
    decorView.setOnSystemUiVisibilityChangeListener(new OnSystemUiVisibilityChangeListener() {
      @Override
      public void onSystemUiVisibilityChange(int visibility) {
        setSystemUiVisilityMode(); // Needed to avoid exiting immersive_sticky when keyboard is displayed
      }
    });
  }

  private View setSystemUiVisilityMode() {
    View decorView = getWindow().getDecorView();
    int options;
    options =
        View.SYSTEM_UI_FLAG_LAYOUT_STABLE
      | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
      | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
      | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
      | View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar
      | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;

    decorView.setSystemUiVisibility(options);
    return decorView;
  }

setupMainWindowDisplayMode() gets called in onCreate().

Beast answered 2/6, 2014 at 23:25 Comment(6)
The third option alone isn't enough. After a lot of testing, I noticed that the OnSystemUiVisibilityChangeListener doesn't always fire. I solved this by adding the first option, but to be sure I also implemented the second option. Good answer :)Dex
do you mean by option one call it in onwindowfocuschanged ?Moriah
Yuck, Android development is truly horrendous sometimes.Lagrange
Very nice, cover most of my problemsPoundal
When a Dialog or a Spinner shows, it has its own Window, so setSystemUiVisilityMode of Activity has no effect and Navigation UI still shows.Androgynous
Has to be the 3 options :DScanlon
E
19

I had the same problem, and I solved it with a simple workaround. Even though I couldn't find the theoretical reason of this workaround, but it worked for me anyway.

It seems like when a volume button is pressed, the 'flags' related to the 'immersive mode' are cleared. And I think that's why the immersive mode is disabled and the immersive mode is not restored automatically.

Therefore, I tried to set the 'flags' after pressing the volume button with 'runnable' object.

So, it works like this:

immersive mode -> volume button pressed(flags cleared) -> 500ms later, the runnable object sets the flags again -> immersive mode restored

1. First, define the runnable object to set the flags

private Runnable decor_view_settings = new Runnable()
{
    public void run() 
    {
        getWindow().getDecorView().setSystemUiVisibility(
                View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                | View.SYSTEM_UI_FLAG_FULLSCREEN
                | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
    }
};

2. Post the runnable object with some delay to a handler when a volume button is pressed

private Handler mHandler = new Handler();

...

@Override 
public boolean onKeyDown(int keyCode, KeyEvent event) 
{
    if(keyCode == KeyEvent.KEYCODE_BACK)
    {
        finish();
    }
    else if(keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP)
    {
        mHandler.postDelayed(decor_view_settings, 500);
    }

    return super.onKeyDown(keyCode, event);
}

I just delayed it for 500ms with no reason, it's not important.

3. The basic code for immersive mode with runnable object

@Override
public void onWindowFocusChanged(boolean hasFocus) 
{
    super.onWindowFocusChanged(hasFocus);

    if(hasFocus) 
    {
        mHandler.post(decor_view_settings);
    }
}

It worked perfectly on my app.

So, when I press a volume button, the immersive mode is disabled and the volume rocker pops up.

after a few seconds, the volume rocker disappears and so does the status bar and the navigation bar.

Hope this work for you.

Executory answered 21/1, 2014 at 9:2 Comment(4)
I had problem even with this "delayed" fix when switching between apps. Calling setSystemUiVisibility twice helped me: without SYSTEM_UI_FLAG_IMMERSIVE_STICKY and then again with SYSTEM_UI_FLAG_IMMERSIVE_STICKY. More details: vitiy.info/…Catiline
Wow, this actually did the trick. The dismiss of the keyboard would not always get rid of the keyboard for us. And waiting for change notice was slightly more affective but still there is some sort of race condition on the older devices, but just hooking into visibility change got rid of the navigation bar when it was time to hid it. Thanks!Tellus
Thanks. I'm displaying the ImageView's Bitmap to fullscreen in a new fragment when clicked. However, a status-bar sized blank space appeared at the bottom after fullscreen. Using View#post(Runnable) in onCreateView() works for me.Portative
This solution is really working and should be the accepted answer! Thanks for the awesome workCindelyn

© 2022 - 2024 — McMap. All rights reserved.