Skip to content

Instantly share code, notes, and snippets.

@chrisbanes
Last active November 5, 2024 13:50
Show Gist options
  • Save chrisbanes/73de18faffca571f7292 to your computer and use it in GitHub Desktop.
Save chrisbanes/73de18faffca571f7292 to your computer and use it in GitHub Desktop.
SystemUiHelper
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.systemuivis;
import android.app.Activity;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.view.WindowManager;
/**
* Helper for controlling the visibility of the System UI across the various API levels. To use
* this API, instantiate an instance of this class with the required level. The level specifies the
* extent to which the System UI's visibility is changed when you call {@link #hide()}
* or {@link #toggle()}.
*/
public final class SystemUiHelper {
/**
* In this level, the helper will toggle low profile mode.
*/
public static final int LEVEL_LOW_PROFILE = 0;
/**
* In this level, the helper will toggle the visibility of the status bar.
* If there is a navigation bar, it will toggle low profile mode.
*/
public static final int LEVEL_HIDE_STATUS_BAR = 1;
/**
* In this level, the helper will toggle the visibility of the navigation bar
* (if present and if possible) and status bar. In cases where the navigation
* bar is present but cannot be hidden, it will toggle low profile mode.
*/
public static final int LEVEL_LEAN_BACK = 2;
/**
* In this level, the helper will toggle the visibility of the navigation bar
* (if present and if possible) and status bar, in an immersive mode. This means that the app
* will continue to receive all touch events. The user can reveal the system bars with an
* inward swipe along the region where the system bars normally appear.
*
* <p>The {@link #FLAG_IMMERSIVE_STICKY} flag can be used to control how the system bars are
* displayed.
*/
public static final int LEVEL_IMMERSIVE = 3;
/**
* When this flag is set, the
* {@link android.view.WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN}
* flag will be set on older devices, making the status bar "float" on top
* of the activity layout. This is most useful when there are no controls at
* the top of the activity layout.
* <p>
* This flag isn't used on newer devices because the <a
* href="http://developer.android.com/design/patterns/actionbar.html">action
* bar</a>, the most important structural element of an Android app, should
* be visible and not obscured by the system UI.
*/
public static final int FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES = 0x1;
/**
* Used with {@link #LEVEL_IMMERSIVE}. When this flag is set, an inward swipe in the system
* bars areas will cause the system bars to temporarily appear in a semi-transparent state,
* but no flags are cleared, and your system UI visibility change listeners are not triggered.
* The bars automatically hide again after a short delay, or if the user interacts with the
* middle of the screen.
*/
public static final int FLAG_IMMERSIVE_STICKY = 0x2;
private static final String LOG_TAG = SystemUiHelper.class.getSimpleName();
private final SystemUiHelperImpl mImpl;
private final Handler mHandler;
private final Runnable mHideRunnable;
/**
* Construct a new SystemUiHelper.
*
* @param activity The Activity who's system UI should be changed
* @param level The level of hiding. Should be either {@link #LEVEL_LOW_PROFILE},
* {@link #LEVEL_HIDE_STATUS_BAR}, {@link #LEVEL_LEAN_BACK} or
* {@link #LEVEL_IMMERSIVE}
* @param flags Additional options. See {@link #FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES} and
* {@link #FLAG_IMMERSIVE_STICKY}
*/
public SystemUiHelper(Activity activity, int level, int flags) {
this(activity, level, flags, null);
}
/**
* Construct a new SystemUiHelper.
*
* @param activity The Activity who's system UI should be changed
* @param level The level of hiding. Should be either {@link #LEVEL_LOW_PROFILE},
* {@link #LEVEL_HIDE_STATUS_BAR}, {@link #LEVEL_LEAN_BACK} or
* {@link #LEVEL_IMMERSIVE}
* @param flags Additional options. See {@link #FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES} and
* {@link #FLAG_IMMERSIVE_STICKY}
* @param listener A listener which is called when the system visibility is changed
*/
public SystemUiHelper(Activity activity, int level, int flags,
OnVisibilityChangeListener listener) {
mHandler = new Handler(Looper.getMainLooper());
mHideRunnable = new HideRunnable();
// Create impl
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mImpl = new SystemUiHelperImplKK(activity, level, flags, listener);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
mImpl = new SystemUiHelperImplJB(activity, level, flags, listener);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
mImpl = new SystemUiHelperImplICS(activity, level, flags, listener);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
mImpl = new SystemUiHelperImplHC(activity, level, flags, listener);
} else {
mImpl = new SystemUiHelperImplBase(activity, level, flags, listener);
}
}
/**
* @return true if the system UI is currently showing. What this means depends on the mode this
* {@link android.example.android.systemuivis.SystemUiHelper} was instantiated with.
*/
public boolean isShowing() {
return mImpl.isShowing();
}
/**
* Show the system UI. What this means depends on the mode this {@link android.example.android.systemuivis.SystemUiHelper} was
* instantiated with.
*
* <p>Any currently queued delayed hide requests will be removed.
*/
public void show() {
// Ensure that any currently queued hide calls are removed
removeQueuedRunnables();
mImpl.show();
}
/**
* Hide the system UI. What this means depends on the mode this {@link android.example.android.systemuivis.SystemUiHelper} was
* instantiated with.
*
* <p>Any currently queued delayed hide requests will be removed.
*/
public void hide() {
// Ensure that any currently queued hide calls are removed
removeQueuedRunnables();
mImpl.hide();
}
/**
* Request that the system UI is hidden after a delay.
*
* <p>Any currently queued delayed hide requests will be removed.
*
* @param delayMillis The delay (in milliseconds) until the Runnable
* will be executed.
*/
public void delayHide(long delayMillis) {
// Ensure that any currently queued hide calls are removed
removeQueuedRunnables();
mHandler.postDelayed(mHideRunnable, delayMillis);
}
/**
* Toggle whether the system UI is displayed.
*/
public void toggle() {
if (mImpl.isShowing()) {
mImpl.hide();
} else {
mImpl.show();
}
}
private void removeQueuedRunnables() {
// Ensure that any currently queued hide calls are removed
mHandler.removeCallbacks(mHideRunnable);
}
/**
* A callback interface used to listen for system UI visibility changes.
*/
public interface OnVisibilityChangeListener {
/**
* Called when the system UI visibility has changed.
*
* @param visible True if the system UI is visible.
*/
public void onVisibilityChange(boolean visible);
}
static abstract class SystemUiHelperImpl {
final Activity mActivity;
final int mLevel;
final int mFlags;
final OnVisibilityChangeListener mOnVisibilityChangeListener;
boolean mIsShowing = true;
SystemUiHelperImpl(Activity activity, int level, int flags,
OnVisibilityChangeListener onVisibilityChangeListener) {
mActivity = activity;
mLevel = level;
mFlags = flags;
mOnVisibilityChangeListener = onVisibilityChangeListener;
}
abstract void show();
abstract void hide();
boolean isShowing() {
return mIsShowing;
}
void setIsShowing(boolean isShowing) {
mIsShowing = isShowing;
if (mOnVisibilityChangeListener != null) {
mOnVisibilityChangeListener.onVisibilityChange(mIsShowing);
}
}
}
/**
* Base implementation. Used on API level 10 and below.
*/
static class SystemUiHelperImplBase extends SystemUiHelperImpl {
SystemUiHelperImplBase(Activity activity, int level, int flags,
OnVisibilityChangeListener onVisibilityChangeListener) {
super(activity, level, flags, onVisibilityChangeListener);
if ((mFlags & SystemUiHelper.FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES) != 0) {
mActivity.getWindow().addFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
}
}
@Override
void show() {
if (mLevel > SystemUiHelper.LEVEL_LOW_PROFILE) {
mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
setIsShowing(true);
}
}
@Override
void hide() {
if (mLevel > SystemUiHelper.LEVEL_LOW_PROFILE) {
mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
setIsShowing(false);
}
}
}
private class HideRunnable implements Runnable {
@Override
public void run() {
hide();
}
}
}
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.systemuivis;
import android.annotation.TargetApi;
import android.app.ActionBar;
import android.app.Activity;
import android.os.Build;
import android.view.View;
import android.view.WindowManager;
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
class SystemUiHelperImplHC extends SystemUiHelper.SystemUiHelperImpl
implements View.OnSystemUiVisibilityChangeListener {
final View mDecorView;
SystemUiHelperImplHC(Activity activity, int level, int flags,
SystemUiHelper.OnVisibilityChangeListener onVisibilityChangeListener) {
super(activity, level, flags, onVisibilityChangeListener);
mDecorView = activity.getWindow().getDecorView();
mDecorView.setOnSystemUiVisibilityChangeListener(this);
}
@Override
void show() {
mDecorView.setSystemUiVisibility(createShowFlags());
}
@Override
void hide() {
mDecorView.setSystemUiVisibility(createHideFlags());
}
@Override
public final void onSystemUiVisibilityChange(int visibility) {
if ((visibility & createTestFlags()) != 0) {
onSystemUiHidden();
} else {
onSystemUiShown();
}
}
protected void onSystemUiShown() {
ActionBar ab = mActivity.getActionBar();
if (ab != null) {
ab.show();
}
mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
setIsShowing(true);
}
protected void onSystemUiHidden() {
ActionBar ab = mActivity.getActionBar();
if (ab != null) {
ab.hide();
}
mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
setIsShowing(false);
}
protected int createShowFlags() {
return View.STATUS_BAR_VISIBLE;
}
protected int createHideFlags() {
return View.STATUS_BAR_HIDDEN;
}
protected int createTestFlags() {
return View.STATUS_BAR_HIDDEN;
}
}
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.systemuivis;
import android.annotation.TargetApi;
import android.app.Activity;
import android.os.Build;
import android.view.View;
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
class SystemUiHelperImplICS extends SystemUiHelperImplHC {
SystemUiHelperImplICS(Activity activity, int level, int flags,
SystemUiHelper.OnVisibilityChangeListener onVisibilityChangeListener) {
super(activity, level, flags, onVisibilityChangeListener);
}
@Override
protected int createShowFlags() {
return View.SYSTEM_UI_FLAG_VISIBLE;
}
@Override
protected int createTestFlags() {
if (mLevel >= SystemUiHelper.LEVEL_LEAN_BACK) {
// Intentionally override test flags.
return View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
}
return View.SYSTEM_UI_FLAG_LOW_PROFILE;
}
@Override
protected int createHideFlags() {
int flag = View.SYSTEM_UI_FLAG_LOW_PROFILE;
if (mLevel >= SystemUiHelper.LEVEL_LEAN_BACK) {
flag |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
}
return flag;
}
}
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.systemuivis;
import android.annotation.TargetApi;
import android.app.ActionBar;
import android.app.Activity;
import android.os.Build;
import android.view.View;
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
class SystemUiHelperImplJB extends SystemUiHelperImplICS {
SystemUiHelperImplJB(Activity activity, int level, int flags,
SystemUiHelper.OnVisibilityChangeListener onVisibilityChangeListener) {
super(activity, level, flags, onVisibilityChangeListener);
}
@Override
protected int createShowFlags() {
int flag = super.createShowFlags();
if (mLevel >= SystemUiHelper.LEVEL_HIDE_STATUS_BAR) {
flag |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
if (mLevel >= SystemUiHelper.LEVEL_LEAN_BACK) {
flag |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
}
}
return flag;
}
@Override
protected int createHideFlags() {
int flag = super.createHideFlags();
if (mLevel >= SystemUiHelper.LEVEL_HIDE_STATUS_BAR) {
flag |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_FULLSCREEN;
if (mLevel >= SystemUiHelper.LEVEL_LEAN_BACK) {
flag |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
}
}
return flag;
}
@Override
protected void onSystemUiShown() {
if (mLevel == SystemUiHelper.LEVEL_LOW_PROFILE) {
// Manually show the action bar when in low profile mode.
ActionBar ab = mActivity.getActionBar();
if (ab != null) {
ab.show();
}
}
setIsShowing(true);
}
@Override
protected void onSystemUiHidden() {
if (mLevel == SystemUiHelper.LEVEL_LOW_PROFILE) {
// Manually hide the action bar when in low profile mode.
ActionBar ab = mActivity.getActionBar();
if (ab != null) {
ab.hide();
}
}
setIsShowing(false);
}
}
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.systemuivis;
import android.annotation.TargetApi;
import android.app.Activity;
import android.os.Build;
import android.view.View;
@TargetApi(Build.VERSION_CODES.KITKAT)
class SystemUiHelperImplKK extends SystemUiHelperImplJB {
SystemUiHelperImplKK(Activity activity, int level, int flags,
SystemUiHelper.OnVisibilityChangeListener onVisibilityChangeListener) {
super(activity, level, flags, onVisibilityChangeListener);
}
@Override
protected int createHideFlags() {
int flag = super.createHideFlags();
if (mLevel == SystemUiHelper.LEVEL_IMMERSIVE) {
// If the client requested immersive mode, and we're on Android 4.4
// or later, add relevant flags. Applying HIDE_NAVIGATION without
// IMMERSIVE prevents the activity from accepting all touch events,
// so we only do this on Android 4.4 and later (where IMMERSIVE is
// present).
flag |= ((mFlags & SystemUiHelper.FLAG_IMMERSIVE_STICKY) != 0)
? View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
: View.SYSTEM_UI_FLAG_IMMERSIVE;
}
return flag;
}
}
@hvisser
Copy link

hvisser commented Aug 29, 2014

Nitpicking: private static final String LOG_TAG = SystemUiHelper.class.getSimpleName(); will turn logging on Proguarded builds pretty useless. Then again, shouldn't log in production builds. So yeah. Nitpicking.

@AKiniyalocts
Copy link

This is great! Thanks!

@angeldevil
Copy link

@Override
protected void onSystemUiShown() {
    if (mLevel == SystemUiHelper.LEVEL_LOW_PROFILE) {
        // Manually show the action bar when in low profile mode.
        ActionBar ab = mActivity.getActionBar();
        if (ab != null) {
            ab.show();
        }
    }

    setIsShowing(false); // Should be true ?
}

@agustinsivoplas
Copy link

Hi, I am trying to hide "for ever" the bottom bar but in your example, If I touch the screen the bars appear again. The idea is use devices to show information to normal users, I do not want that the users interact with the tablet. Do you have any idea? All the best, Agustin

@alexdzeshko
Copy link

angeldevil commented 18 days ago

@Override
protected void onSystemUiShown() {
    if (mLevel == SystemUiHelper.LEVEL_LOW_PROFILE) {
        // Manually show the action bar when in low profile mode.
        ActionBar ab = mActivity.getActionBar();
        if (ab != null) {
            ab.show();
        }
    }

    setIsShowing(false); // Should be true ?
}

Yes, i think there is a mistake. And in

@Override
    protected void onSystemUiHidden() {
        if (mLevel == SystemUiHelper.LEVEL_LOW_PROFILE) {
            // Manually hide the action bar when in low profile mode.
            ActionBar ab = mActivity.getActionBar();
            if (ab != null) {
                ab.hide();
            }
        }

        setIsShowing(true); //should be false
    }

as well. Please, amend this.
Anyway, thanks for great stuff :)

@b95505017
Copy link

maybe it would be better to add reset() to clear all flags :)

@mr-thierry
Copy link

Any update for Lolipop?

@hurelhuyag
Copy link

How to use? Below example is right?

    SystemUiHelper uiHelper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.video);
        uiHelper = new SystemUiHelper(this, SystemUiHelper.LEVEL_LEAN_BACK, 0);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_UP:
                if (uiHelper.isShowing()){
                    uiHelper.hide();
                }else{
                    uiHelper.show();
                }
                break;
        }
        return super.onTouchEvent(event);
    }

@joshallenit
Copy link

Hi, great code, thanks! The visibility callback is reversed for JellyBean+. Merge my fork for the fix.

https://gist.github.com/joshallenit/03b7966d2e19f3d94313

Copy link

ghost commented Jul 17, 2015

Hi, very useful!! Thanks!

@vuhung3990
Copy link

how to use ? please show a sample code, thank you

@kaoruAngel
Copy link

@zhanghai
Copy link

Note that sometimes View.setSystemUiVisibility() will not function until next layout. You need to add mDecorView.requestLayout() to SystemUiHelperImplHC.show|hide() to fix this.

@zhanghai
Copy link

zhanghai commented Dec 1, 2015

I've packaged this gist with the fix above, and a sample on how to use Toolbar with immersive mode.

In case someone is interested, please checkout DreaminginCodeZH/SystemUiHelper.

@IvanPosohov
Copy link

Can someone explain me why method createShowFlags() in SystemUiHelperImplJB.java in some cases returns flags View.SYSTEM_UI_FLAG_LAYOUT_STABLE, View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN, View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION (lines 37-42)?

@rajeshkumar12345
Copy link

hai, I am stuck with status bar hiding. Let me explain I am Implementing activity and also place manifest file in android:windowSoftInputMode="adjustResize" for keyboard place below edittext, it is working fine but i want also hide navigation bar, so i am using "Theme.Holo.Light.NoActionBar.Fullscreen" theme. But when keyboard appearing app navigation bar also scrolling. How can i restrict scrolling navigation bar and hide notification bar when keyboard appearing. And also i am using SystemUiHelper systemUiHelper = new SystemUiHelper(this,SystemUiHelper.LEVEL_IMMERSIVE,SystemUiHelper.FLAG_IMMERSIVE_STICKY);
systemUiHelper.toggle(); like that. But i didn't get any chance to resolve my problem. Please tell me.
screenshot_2016-03-19-12-13-56
screenshot_2016-03-19-12-16-17

@mohocp
Copy link

mohocp commented Jan 6, 2017

The RecycleView layout in landscape orientation shift to right after I used uiHelper.show(), Please any one can help to over come this problem

@deshan
Copy link

deshan commented May 18, 2017

Any help for getting this work for Android 7.0?
Checked in S8 +, but see huge blank area in the top and bottom

Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment