Android: How to hide the System UI properly
Asked Answered
E

6

6

I'm developing a video player application and I've faced to a problem. I have a custom video controller which contains a fullscreen button and I want hide the System UI (navigation and status bars) when the user enters to fullscreen mode. I've tried to do something like this, but it doesn't work properly. My application is like the screenshot below:

enter image description here

When I click the fullscreen button, I change orientation to landscape and I hide the System UI, but my player doesn't get fullscreen. It looks like the screen below:

Status bar hides and navigation bar hides too, but player doesn't take the whole screen

Also, when I tap the screen I want to show my video controller, but the System UI shows the below content, instead:

System UI shows when I tap on screen

So, how can I implement this behavior? Below are my methods for fullscreen and hiding/showing the System UI:

@Override
public void toggleFullscreen() {
    LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mVideoContainer.getLayoutParams();
    if (!mFullscreen) {
             setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
        hideSystemUI();
        DisplayMetrics metrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metrics);
        params.width = metrics.widthPixels;
        params.height = metrics.heightPixels;
        params.setMargins(0, 0, 0, 0);
    } else {
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        showSystemUI();
        DisplayMetrics metrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metrics);
        params.width = metrics.widthPixels;
        params.height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 300, metrics);
            params.setMargins(0, 0, 0, 0);
    }
    mVideoContainer.setLayoutParams(params);
    mFullscreen = !mFullscreen;
}

private void hideSystemUI() {
    View decorView = getWindow().getDecorView();
    int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
    if (Build.VERSION.SDK_INT < 16) {
            getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN)
    } else {
        uiOptions |= View.SYSTEM_UI_FLAG_FULLSCREEN;
    }
    decorView.setSystemUiVisibility(uiOptions);
}

private void showSystemUI() {
    View decorView = getWindow().getDecorView();
    decorView.setSystemUiVisibility(0);
}

Here's my layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/video_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <FrameLayout
        android:id="@+id/videoSurfaceContainer"
        android:layout_width="match_parent"
        android:layout_height="300dp" >

        <SurfaceView
            android:id="@+id/videoSurface"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </FrameLayout>
</LinearLayout>
Ersatz answered 22/5, 2016 at 23:12 Comment(0)
L
9

I include this in every activity that I want to hide the nav bar and status bar:

    public void hideToolBr(){

        // BEGIN_INCLUDE (get_current_ui_flags)
        // The UI options currently enabled are represented by a bitfield.
        // getSystemUiVisibility() gives us that bitfield.
        int uiOptions = getWindow().getDecorView().getSystemUiVisibility();
        int newUiOptions = uiOptions;
        // END_INCLUDE (get_current_ui_flags)
        // BEGIN_INCLUDE (toggle_ui_flags)
        boolean isImmersiveModeEnabled =
                ((uiOptions | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) == uiOptions);
        if (isImmersiveModeEnabled) {
            Log.i(Constants.TAG_DEF, "Turning immersive mode mode off. ");
        } else {
            Log.i(Constants.TAG_DEF, "Turning immersive mode mode on.");
        }

        // Navigation bar hiding:  Backwards compatible to ICS.
        if (Build.VERSION.SDK_INT >= 14) {
            newUiOptions ^= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
        }

        // Status bar hiding: Backwards compatible to Jellybean
        if (Build.VERSION.SDK_INT >= 16) {
            newUiOptions ^= View.SYSTEM_UI_FLAG_FULLSCREEN;
        }

        // Immersive mode: Backward compatible to KitKat.
        // Note that this flag doesn't do anything by itself, it only augments the behavior
        // of HIDE_NAVIGATION and FLAG_FULLSCREEN.  For the purposes of this sample
        // all three flags are being toggled together.
        // Note that there are two immersive mode UI flags, one of which is referred to as "sticky".
        // Sticky immersive mode differs in that it makes the navigation and status bars
        // semi-transparent, and the UI flag does not get cleared when the user interacts with
        // the screen.
        if (Build.VERSION.SDK_INT >= 18) {
            newUiOptions ^= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
        }

        getWindow().getDecorView().setSystemUiVisibility(newUiOptions);
        //END_INCLUDE (set_ui_flags)

    }

Then in my activity's onCreate method, I call this after setContentView()

hideToolBr();

Then all the user has to do is swipe up from the bottom or swipe down from the top to bring up the status bar or nav bar. Google calls this "immersive mode". It gives the developer full use of the devices' screen real-estate.

Taken from Googles example of immersive mode, calling this shows the system UI:

    // This snippet shows the system bars. It does this by removing all the flags
// except for the ones that make the content appear under the system bars.
private void showSystemUI() {
    mDecorView.setSystemUiVisibility(
            View.SYSTEM_UI_FLAG_LAYOUT_STABLE
            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
            | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
}

You can find more information here.

Edit:

Add this to your styles.xml as well

    <style name="YourTheme.NoActionBar">
    <item name="windowActionBar">false</item>
    <item name="windowNoTitle">true</item>
    <item name="android:colorForeground">@color/colorPrimaryDark</item>
</style>
Lovemaking answered 22/5, 2016 at 23:45 Comment(15)
it still leaves empty space after entering to fullscreen at navigation bar's place :(Ersatz
@OlegRyabtsev Does your layout match the parent in your xml android:layout_height="match_parent"?Lovemaking
I have a LinearLayout as a root layout with layout_height="match_parent", and inside this I have a FrameLayout with 300dp height. Inside FrameLayout is SurfaceView with width and height equal to match_parent. And I try change size of FrameLayout dynamically when click on fullscreen buttonErsatz
@OlegRyabtsev So your main LinearLayout fills the screen but your FrameLayout doesn't adjust to fill the space where the nav bar was. Is that correct?Lovemaking
@OlegRyabtsev Could you post the layout you're having problems with so I can test it?Lovemaking
@OlegRyabtsev Ok, I apologize but I'm a bit confused here. You have your FrameLayout height set to 300dp and you want it to fill the screen right? Are you expecting to fill the area below the SurfaceView with something? Why not set your FrameLayout height to match_parent and your SurfaceView height to 300dp? Also, see my edit.Lovemaking
@SteveC.The fact is that I want fill this area with RecyclerView in future. That's why I set height as constantErsatz
@OlegRyabtsev Ok. After testing. You can reverse the height params of FrameLayout and SurfaceView and when you're ready to include a RecyclerView or any view you can just use the "include" attributeLovemaking
@OlegRyabtsev I did it that way and it fills the screen both in portrait and landscapeLovemaking
@SteveC.Do you mean that I need set height of FrameLayout to match_parent, height of SurfaceView to 300dp and include RecyclerView to FrameLayout?Ersatz
@OlegRyabtsev Yes. Then when you're ready to add an additional layout to the view, just use the "include" attribute within your FrameLayout below the SurfaceView and you should be good.Lovemaking
@SteveC.Ok, I'll try. Thanks for helpErsatz
@SteveC. Wouldn't it be best to create another class called FullscreenActivity (or something like that) that extends the AppCompatActivity and put the "hideToolBr()" method in there so your fullscreen activities would extend from FullscreenActivity? Then in FullScreenActivity's onCreate() method you can call hideToolBr(). This way you won't need to copy paste code? I've only been tinkering around in this android/java stuff for a couple of days, so this is a serious question. Is there a better way than this/the suggestion you provided? I really don't like copying/repeating code..Tsarevna
@Tsarevna If I don't have that many activities, I'll just copy paste but in a large scale environment it would be more productive to create a base activity class that holds commonly used methods likes hiding ui then extending that class in all your activities.Lovemaking
How come mContentView and mControlsView are not set to anything? I'm getting a nullpointerexception and I don't know what to do here.Claude
S
6

Just call these methods when you want to hide/show the system UI.

private void hideSystemUI() {
    View decorView = getActivity().getWindow().getDecorView();
    int uiOptions = decorView.getSystemUiVisibility();
    int newUiOptions = uiOptions;
    newUiOptions |= View.SYSTEM_UI_FLAG_LOW_PROFILE;
    newUiOptions |= View.SYSTEM_UI_FLAG_FULLSCREEN;
    newUiOptions |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
    newUiOptions |= View.SYSTEM_UI_FLAG_IMMERSIVE;
    newUiOptions |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
    decorView.setSystemUiVisibility(newUiOptions);
}

private void showSystemUI() {
    View decorView = getActivity().getWindow().getDecorView();
    int uiOptions = decorView.getSystemUiVisibility();
    int newUiOptions = uiOptions;
    newUiOptions &= ~View.SYSTEM_UI_FLAG_LOW_PROFILE;
    newUiOptions &= ~View.SYSTEM_UI_FLAG_FULLSCREEN;
    newUiOptions &= ~View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
    newUiOptions &= ~View.SYSTEM_UI_FLAG_IMMERSIVE;
    newUiOptions &= ~View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
    decorView.setSystemUiVisibility(newUiOptions);
}
Siliculose answered 1/6, 2020 at 6:59 Comment(1)
I had a problem that the titleBar would not hide, but it can be solved with getSupportActionBar().hide(); and getSupportActionBar().show(); for anyone who encounters the same issueHepsiba
L
2

To hide system UI there is a clean and scalable approach I've come up recently with. It supports all Android versions including Api level 31, 30, and before that.

object SystemBarsCompat {
    private val api: Api =
        when {
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> Api31()
            Build.VERSION.SDK_INT == Build.VERSION_CODES.R -> Api30()
            else -> Api()
        }

    fun hideSystemBars(window: Window, view: View, isImmersiveStickyMode: Boolean = false) =
        api.hideSystemBars(window, view, isImmersiveStickyMode)

    fun showSystemBars(window: Window, view: View) = api.showSystemBars(window, view)

    fun areSystemBarsHidden(view: View): Boolean = api.areSystemBarsHidden(view)

    @Suppress("DEPRECATION")
    private open class Api {
        open fun hideSystemBars(window: Window, view: View, isImmersiveStickyMode: Boolean = false) {
            val flags = View.SYSTEM_UI_FLAG_FULLSCREEN or
                View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
                View.SYSTEM_UI_FLAG_HIDE_NAVIGATION

            view.systemUiVisibility = if (isImmersiveStickyMode) {
                flags or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
            } else {
                flags or
                    View.SYSTEM_UI_FLAG_IMMERSIVE or
                    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
            }
        }

        open fun showSystemBars(window: Window, view: View) {
            view.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
        }

        open fun areSystemBarsHidden(view: View) = view.systemUiVisibility and View.SYSTEM_UI_FLAG_HIDE_NAVIGATION != 0
    }

    @Suppress("DEPRECATION")
    @RequiresApi(Build.VERSION_CODES.R)
    private open class Api30 : Api() {

        open val defaultSystemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE

        override fun hideSystemBars(window: Window, view: View, isImmersiveStickyMode: Boolean) {
            window.setDecorFitsSystemWindows(false)
            view.windowInsetsController?.let {
                it.systemBarsBehavior =
                    if (isImmersiveStickyMode) WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
                    else defaultSystemBarsBehavior
                it.hide(WindowInsets.Type.systemBars())
            }
        }

        override fun showSystemBars(window: Window, view: View) {
            window.setDecorFitsSystemWindows(false)
            view.windowInsetsController?.show(WindowInsets.Type.systemBars())
        }

        override fun areSystemBarsHidden(view: View) = !view.rootWindowInsets.isVisible(WindowInsets.Type.navigationBars())
    }

    @RequiresApi(Build.VERSION_CODES.S)
    private class Api31 : Api30() {
        override val defaultSystemBarsBehavior = WindowInsetsController.BEHAVIOR_DEFAULT
    }
}

And for example to hide system bars, it can be called from a Fragment:

SystemBarsCompat.hideSystemBars(requireActivity().window, view)
Lepidus answered 5/2, 2022 at 19:46 Comment(0)
P
1
**For Kotlin:** 

  private fun hideSystemUI() {
       val decorView: View = this.window.decorView
       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
           window.setDecorFitsSystemWindows(false)
       } else {
  
            val uiOptions = decorView.systemUiVisibility
            var newUiOptions = uiOptions
            newUiOptions = newUiOptions or View.SYSTEM_UI_FLAG_LOW_PROFILE
            newUiOptions = newUiOptions or View.SYSTEM_UI_FLAG_FULLSCREEN
            newUiOptions = newUiOptions or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
            newUiOptions = newUiOptions or View.SYSTEM_UI_FLAG_IMMERSIVE
            newUiOptions = newUiOptions or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
            decorView.systemUiVisibility = newUiOptions
        }
}
    
 private fun showSystemUI() {
        val decorView: View = this.window.decorView
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
          window.setDecorFitsSystemWindows(true)
        } else {
  
            val uiOptions = decorView.systemUiVisibility
            var newUiOptions = uiOptions
            newUiOptions = newUiOptions and View.SYSTEM_UI_FLAG_LOW_PROFILE.inv()
            newUiOptions = newUiOptions and View.SYSTEM_UI_FLAG_FULLSCREEN.inv()
            newUiOptions = newUiOptions and View.SYSTEM_UI_FLAG_HIDE_NAVIGATION.inv()
            newUiOptions = newUiOptions and View.SYSTEM_UI_FLAG_IMMERSIVE.inv()
            newUiOptions = newUiOptions and View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY.inv()
            decorView.systemUiVisibility = newUiOptions
        }
    }
Probably answered 21/11, 2020 at 11:16 Comment(2)
systemUiVisibility is deprecatedUndercurrent
@FerozKhan use this instead of systemUiVisibility: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { window.setDecorFitsSystemWindows(false) } else { window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN }Probably
L
0

Go to https://developer.android.com/training/system-ui/immersive

They had explained this system very properly. And add below style in your activity theme

<style name="BlackActionTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/black</item>
        <item name="colorPrimaryDark">@color/black</item>
        <item name="colorAccent">@color/colorAccent</item>

        <item name="android:windowNoTitle">true</item>
        <item name="android:windowActionBar">false</item>
    </style>
Lymn answered 12/9, 2019 at 12:14 Comment(0)
P
0

In API version 30 (R) setting window.decorView.systemUiVisibility is deprecated. Use the window.insetsController instead:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
    window.insetsController?.let {
        it.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
        window.navigationBarColor = getColor(R.color.semi_transparent)
        it.hide(WindowInsets.Type.systemBars())
    }
} else {
    // see other answers
}

For a detailed explanation see https://medium.com/swlh/modifying-system-ui-visibility-in-android-11-e66a4128898b

Portauprince answered 11/1, 2021 at 8:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.