市面上很多 App 启动的时候都有一个几秒钟启动页,可以用来放一些广告,或者做一些宣传自家品牌的内容,而且是沉浸式效果,看起来十分舒服。
那么今天就来聊聊沉浸式的实现。
什么是沉浸式
估计问十个人会有十个回答,很多人都说不上到底什么才是沉浸式,原因是国内许多博客都张冠李戴说什么沉浸式状态栏或者沉浸式效果之类的名词,让人摸不着头脑。
Android 官方从来没有给出过沉浸式状态栏这样的命名,只有沉浸式模式(Immersive Mode)这种说法,不过倒是有部分国内的 ROM 给出了「沉浸式状态栏」这种设置。
所以实际上「沉浸式状态栏」可以理解为透明状态栏,而真正的「沉浸式模式」则是如游戏或视频状态下的全屏效果。
整个屏幕显示都是游戏的内容,没有状态栏也没有导航栏,用户玩游戏的时候就可以完全沉浸在游戏当中,而不会被一些系统的界面元素所打扰,这就是沉浸式模式。
界面元素
要实现沉浸式,首先要了解界面上有哪些元素是需要隐藏的,来看看这张图:
可以看到,有状态栏、ActionBar
、导航栏等。而打造沉浸式模式的用户体验,就是要将这些系统元素全部隐藏,只留下主体内容部分。
实践
布局
启动页很多情况下是一张图片,我从『网易云音乐』中偷了一张图,布局很简单:
<FrameLayout 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=".activity.SplashActivity">
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/splash_bg" />
</FrameLayout>
初始效果如下:
隐藏 ActionBar
隐藏 ActionBar
是项目中常用的场景,我们可以在指定其 android:theme
为 NoActionBar
类型的主题,也可以在逻辑代码中动态隐藏:
class SplashActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
...
supportActionBar?.hide()
}
}
效果如下:
隐藏状态栏
接下来是隐藏状态栏,我通过重写 onWindowFocusChanged()
方法来实现:
class SplashActivity : AppCompatActivity() {
...
override fun onWindowFocusChanged(hasFocus: Boolean) {
...
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN
}
}
这里先获取到了当前界面的 DecorView
,然后调用它的 setSystemUiVisibility()
方法来设置系统 UI 元素的可见性。其中,SYSTEM_UI_FLAG_FULLSCREEN
表示全屏的意思,也就是会将状态栏隐藏。效果如下:
另外,根据 Android 的设计建议,ActionBar
是不应该独立于状态栏而单独显示的,因此状态栏如果隐藏了,则要确保 ActionBar
也进行了隐藏。
透明状态栏
那如果想实现所谓的「沉浸式状态栏」呢?
上面提到,所谓的「沉浸式状态栏」实际上是透明状态栏,我们可以借助另外一种 UI FLAG 来实现:
class SplashActivity : AppCompatActivity() {
...
override fun onWindowFocusChanged(hasFocus: Boolean) {
...
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
window.statusBarColor = Color.TRANSPARENT
}
}
要实现这种效果,修改为 SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
,表示会让应用的主体内容占用系统状态栏的空间,网上有文章提到需要结合 SYSTEM_UI_FLAG_LAYOUT_STABLE
一起使用,但我测试发现似乎不需要也能实现该效果,不知道是不是版本问题。
主体内容占用状态栏的空间还不够,因为此时状态栏依然有着色,我们再将其置为透明即可。
透明导航栏
其实导航栏在不同手机上是不一定存在的,比如小米早期的手机,底部的三大金刚键是不在屏幕范围内的,而华为早期的一些手机则是在屏幕内,近几年手势全面屏的普及,又可以由用户自己决定是否显示三大金刚键,所以只有在屏幕内的才叫导航栏。
透明导航栏和透明状态栏的实现方式相似:
class SplashActivity : AppCompatActivity() {
...
override fun onWindowFocusChanged(hasFocus: Boolean) {
...
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
window.navigationBarColor = Color.TRANSPARENT
}
}
使用 SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
使应用主体内容占用导航栏位置,并将导航栏设置为透明色。
隐藏导航栏
隐藏导航栏的实现方式和上面相似,也是使用不同的 UI FLAG:
class SplashActivity : AppCompatActivity() {
...
override fun onWindowFocusChanged(hasFocus: Boolean) {
...
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN
}
}
同时使用 SYSTEM_UI_FLAG_FULLSCREEN
和 SYSTEM_UI_FLAG_HIDE_NAVIGATION
可以实现将状态栏和导航栏一起隐藏。这种方式虽然看上去好像是全屏化了,但会有个问题,我们触摸屏幕的任意位置或边缘划动会退出全屏,使状态栏和导航栏显示出来,并且无法再次隐藏:
这显然不是我们想要的效果,因此这种模式的使用场景比较有限。
沉浸式
如何才能实现真正的沉浸式效果呢?
同样,还是继续组合 UI FLAG:
class SplashActivity : AppCompatActivity() {
...
override fun onWindowFocusChanged(hasFocus: Boolean) {
...
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
}
}
触摸屏幕并不会退出全屏,且仅在屏幕边缘向中央划动的时候才会呼出状态栏和导航栏,如无操作则在几秒后折叠,重新回到全屏,很符合我们平时玩游戏或看视频时的操作。
不过,网上的博客在介绍这种方式的时候通常会用到 6 种 UI FLAG:
class SplashActivity : AppCompatActivity() {
...
override fun onWindowFocusChanged(hasFocus: Boolean) {
...
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
}
}
为何只需 3 个 UI FLAG 组合就能实现的效果,大家都选用将这 6 个同时写入呢?
经过各种我想到的情况进行测试,终于找到了区别,当我们长按 POWER 键的时候,会有如下不同:
长按 POWER 键会弹出一个菜单遮挡在最顶部,同时该界面会短暂退出沉浸式,仅使用三种 UI FLAG 的时候状态栏和导航栏将显示为默认状态,而使用六种 UI FLAG 的时候状态栏和导航栏将会显示为透明状态,不用我说你也知道,透明状态交互会更加友好。