状态栏及导航栏相关知识小结

在开发应用的过程中,会遇见一些状态栏和导航栏相关的问题,在此记录一下。本文主要目的是记录我在开发中遇到的状态栏和导航栏相关的问题,以便日后查看方便,如果可以帮到你,真是再好不过了。

本文主要分为以下几个部分:

catalog.png

1. 基本概念

首先需要明白几个概念:

  • 状态栏(StatusBar):指屏幕最顶端,显示时间、电量、推送图标那一栏,每个手机都有状态栏。
  • 标题栏(TitleBar):指状态栏下,显示“返回键”、“标题文字”那一栏,根据需求而定是否有标题栏,标题栏可以使用 Toolbar 或者 ActionBar 控件实现
  • 导航栏(NavigationBar):指屏幕最下端,有“返回键”、“Home键”、”菜单键“那一栏。导航栏是虚拟的,不是每个手机都有的。
    状态栏、标题栏、导航栏可以参照下图:


    statusbar&navigationbar.png
  • 沉浸式模式(Immersive Mode):沉浸式就是要给用户提供完全沉浸的体验,使用户有一种置身于虚拟世界之中的感觉。 ---- Android状态栏微技巧,带你真正理解沉浸式模式

    沉浸式模式本质上就是把状态栏、导航栏隐藏,将应用界面全屏化。

    最常见的沉浸式模式应用在游戏和视频类应用中,比如爱奇艺的全屏播放,如下图所示:


    aiqiyi.png
  • 透明状态栏:如下图所示,透明状态栏就是让应用界面背景利用系统状态栏空间,让应用界面背景和系统状态栏融为一体。


    transparentstatusbar.png

需要注意的是,并没有沉浸式状态栏这一概念,只有沉浸式模式透明状态栏的概念。

2. 实践效果

2.1 淡化状态栏和导航栏

Android 4.0(API level 14)及之后 的版本中,可以实现使状态栏和导航栏淡化的效果(Dimming the System Bars)

效果:


dimming.gif

注意观察状态栏和导航栏的变化。

代码:

/**
 * 淡化状态栏和导航栏
 */
private fun dimmingStatusBar() {
    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
        // This example uses decor view, but you can use any visible view.
        window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LOW_PROFILE
    }
}

说明:

  • SYSTEM_UI_FLAG_LOW_PROFILE 标志并不会重新分配界面中 UI 的大小,只是 StatusBar 和 NavigationBar 的相关图标会被弱化
  • 一旦用户触摸 StatusBar 和 NavigationBar 相关区域,系统便清除掉了 SYSTEM_UI_FLAG_LOW_PROFILE 标志
  • SYSTEM_UI_FLAG_LOW_PROFILE 标志只可以在 Android 4.0(API level 14) 及之后的版本中使用
  • 可以使用代码手动清除 SYSTEM_UI_FLAG_LOW_PROFILE 标志,代码如下所示:
private fun clearSystemUIFlag() {
    window.decorView.systemUiVisibility = 0
}

2.2 隐藏状态栏

隐藏状态栏分为两种情况:

  • 在 Android 4.0(API level 14) 及之下的版本中隐藏状态栏
  • 在 Android 4.0(API level 14) 之上的版本中隐藏状态栏

2.2.1 在 Android 4.0 及之下

在 Android 4.0 及之下的版本中,可以通过设置 WindowManager 的 flag 实现隐藏状态栏的效果,设置 WindowManager 的 flag 有两种方式:

代码:

  • 通过编写代码设置 WindowManager 的 flag
public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // If the Android version is lower than Jellybean, use this call to hide
        // the status bar.
        if (Build.VERSION.SDK_INT < 16) {
            getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                    WindowManager.LayoutParams.FLAG_FULLSCREEN);
        }
        setContentView(R.layout.activity_main);
    }
    ...
}
  • 通过设置 AndroidManifest.xml 中 Activity 的 theme 设置 WindowManager 的 flag

    <!-- style.xml -->
    <resources>
    
      <!-- Base application theme. -->
      <style name="BaseAppTheme" parent="Theme.AppCompat.Light.NoActionBar">
          <!-- Customize your theme here. -->
          <item name="colorPrimary">@color/colorPrimary</item>
          <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
          <item name="colorAccent">@color/colorAccent</item>
      </style>
    
      <style name="FullScreenTheme" parent="BaseAppTheme">
          <item name="android:windowFullscreen">true</item>
      </style>
    
      ...
    
    </resources>
    
    <!-- AndroidManifest.xml -->
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
            package="com.lijiankun24.statusbarpractice">
    
      <application
          android:icon="@mipmap/ic_launcher"
          android:label="@string/app_name"
          android:theme="@style/BaseAppTheme"
          ... >
    
          <activity
              android:name=".activity.MainActivity"
              android:theme="@style/FullScreenTheme">
          </activity>
    
          ...
      </application>
    
    </manifest>
    

说明:

  • 通过设置 Activity 的 Theme实现隐藏状态栏有如下优点:
    • 简单而且不易出错
    • UI 切换更流畅,因为系统在实例化 Activity 对象之前就已经获得了渲染 UI 界面的相关信息
  • 通过编写代码设置 WindowManager 的 flag 的方式更容易控制系统 UI 的显示和隐藏
  • 现在市场上 Android 4.0 之下的手机已经很少了,而且很多应用最低版本都在 Android 4.0 之上,所以这点可以依情况而定

2.2.2 在 Android 4.0 之上

效果:


hidestatusbar.gif

代码:

private fun hideStatusBar() {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
        window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN)
    } else {
        window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN or
                View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
                View.SYSTEM_UI_FLAG_LAYOUT_STABLE
    }
}

说明:

  • 如果只使用 SYSTEM_UI_FLAG_FULLSCREEN 标志,在状态栏区域向下滑动出现状态栏的时候,内容区域会出现一个挤压的效果,如下图所示:
    hidestatusbar1.gif
  • 可以使用 SYSTEM_UI_FLAG_LAYOUT_FULLSCREE 标志,让应用的内容区域显示在状态栏的后面,还可以配合 SYSTEM_UI_FLAG_LAYOUT_STABLE 标志使用,让布局保持稳定
  • 一旦这些标志位被清除,则需要重新设置让状态栏隐藏,可以通过监听状态栏和导航栏的可见性,判断状态栏和导航栏是否可见
  • 在不同的位置设置 UI flag 是有区别的。比如,如果在 onCreate() 方法中隐藏状态栏,那当用户按下 Home 键的时候,状态栏重新显示,再打开应用重新回到这个 Activity 的时候,用户可以看到状态栏,因为这时不会调用 onCreate() 方法。如果在 onResume() 或者 onWindowFocusChanged() 就可以避免上面这种情况
  • 只有当调用 setSystemUiVisibility()View 是可见的时候,setSystemUiVisibility() 方法才会起作用
  • 界面的切换会导致 setSystemUiVisibility() 的设置失效
  • 如果设置内容区域在状态栏的背后,那当状态栏显示的时候会遮挡住一部分内容区域,为防止这种情况发生,只需要在布局文件中添加 android:fitsSystemWindows 属性(值为true),就可以解决这种问题,效果如下图所示:
    fitsSystemWindows2.png

代码如下所示:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context="com.lijiankun24.statusbarpractice.activity.FitsSystemWindowsActivity">

  <TextView
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:background="@android:color/holo_blue_light"
      android:fitsSystemWindows="true"
      android:text="@string/app_name"
      android:textColor="@android:color/white"
      android:textSize="28sp"/>
</android.support.constraint.ConstraintLayout>

2.3 隐藏导航栏

效果:


hidenavigationbar.gif

代码:

class HideNavigationbarActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        hideNavigationBar()
        setContentView(R.layout.activity_hide_navigationbar)
    }

    private fun hideNavigationBar() {
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
            window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
                    View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
                    View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
                    View.SYSTEM_UI_FLAG_FULLSCREEN or
                    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
        }
    }
}

说明:

  • 按照一个通用的规则,在隐藏导航栏的时候,一般也需要隐藏状态栏
  • 通过这种方式隐藏导航栏和状态栏之后,触摸屏幕的任何区域,导航栏和状态栏都会重新出现且不会再消失,如果想让导航栏和状态栏消失,则需要手动重新设置 UI flag
  • 如果想让内容区域出现在导航栏的后面,则需要配合使用 SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 标志,并且最好配合使用 SYSTEM_UI_FLAG_LAYOUT_STABLE 使布局保持稳定
  • SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATIONSYSTEM_UI_FLAG_HIDE_NAVIGATION 可以在 Android 4.1 (API level 15)使用

2.4 沉浸式模式

效果:

SYSTEM_UI_FLAG_IMMERSIVE :

immersive.gif

SYSTEM_UI_FLAG_IMMERSIVE_STICKY :

immersive1.gif

代码:

class ImmersiveActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        hideSystemUI()
        setContentView(R.layout.activity_immersive)
    }

    private fun hideSystemUI() {
        window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
                View.SYSTEM_UI_FLAG_FULLSCREEN or
                View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
                View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
                View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
                View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
//                or View.SYSTEM_UI_FLAG_IMMERSIVE
    }
}

说明:

  • 真正的沉浸式模式如上面两个图所示,Activity 可以接收到所有的触摸事件
  • 当使用 SYSTEM_UI_FLAG_IMMERSIVE 标志时,用户滑动状态栏和导航栏边缘的时候,状态栏和导航栏会出现,并且不会再消失,
  • 当使用 SYSTEM_UI_FLAG_IMMERSIVE_STICKY 标志时,用户滑动状态栏和导航栏边缘的时候,状态栏和导航栏会出现,但是和 SYSTEM_UI_FLAG_IMMERSIVE 不同的是,过一会儿状态栏和导航栏会自动消失
  • 如果设置了 View.OnSystemUiVisibilityChangeListener 监听器,SYSTEM_UI_FLAG_IMMERSIVE 会触发 OnSystemUiVisibilityChangeListener 监听器,但是 SYSTEM_UI_FLAG_IMMERSIVE_STICKY 不会触发 OnSystemUiVisibilityChangeListener 监听器

2.5 监听状态栏和导航栏可见性

可以通过 View.OnSystemUiVisibilityChangeListener 为该 View 设置状态栏和导航栏可见性的监听器,代码如下所示:

class RespondingActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        respondingSystemUI()
        setContentView(R.layout.activity_responding)
        window.decorView.setOnSystemUiVisibilityChangeListener {
            L.i("visibility is " + it)
        }
    }

    private fun respondingSystemUI() {
        window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN or
                View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
                View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
                View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
                View.SYSTEM_UI_FLAG_IMMERSIVE
    }
}

2.6 透明状态栏和导航栏

效果:


transparent.gif

代码:

class TransparentNavigationbarActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        transparentingNavigationbar()
        setContentView(R.layout.activity_transparent_navigationbar)
    }

    private fun transparentingNavigationbar() {
        var uiFlag = 0
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            uiFlag = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
                    View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
                    View.SYSTEM_UI_FLAG_FULLSCREEN or
                    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            uiFlag = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or uiFlag
        }
        window.decorView.systemUiVisibility = uiFlag
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            window.statusBarColor = Color.TRANSPARENT
            window.navigationBarColor = Color.TRANSPARENT
        }
    }
}

说明:

  • 除了可以设置状态栏和导航栏为透明,还可以设置为其他颜色
  • 只可以在 Android 5.0(API level 21)及以上的 Android 版本中设置状态栏和导航栏的颜色

3 总结

本文主要介绍了状态栏、导航栏、标题栏、沉浸式模式和透明状态栏的概念,以及实现隐藏状态栏、隐藏导航栏、沉浸式模式和透明状态栏的方式。

3.1 版本要求

实现的主要方式是通过设置 window.decorView.systemUiVisibility 的属性值实现的,但是不是所有的 Android 版本都可以实现上述那些效果的,实现上述效果的版本要求如下图所示:

效果 版本要求
淡化状态栏和导航栏 Version >= 14
隐藏状态栏 全部版本
隐藏导航栏 Version >= 16
沉浸式模式 Version >= 19
透明状态栏 Version >= 21

3.2 相关文章

本文中涉及到的代码在 Github 上面 StatusBarPractice

本文中只涉及到我在实现开屏广告全屏过程中遇到的一些问题,但是还有其他很多相关的问题没有涉及到,下面有一些相关文章,讲的都很详细:

随手记Android沉浸式状态栏的踩坑之路 ---- 刘玲

管理System UI (状态栏 + 导航栏) ---- ShenJC

Android 系统状态栏沉浸式/透明化完整解决方案 ---- btman

Android 沉浸式 (透明) 状态栏适配 ---- xiaoyanger

Android状态栏微技巧,带你真正理解沉浸式模式 ---- 郭霖

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容