刨根问底-论Android“沉浸式”

网上谈论“沉浸式”的文章多的不可胜数,有人把“沉浸式”叫做“沉浸式状态栏”,还有人称作为“透明状态栏”“变色状态栏”等。

这里我们先给出“沉浸式”的直观感受,引用“知乎”网友的答案

使用带虚拟键的手机才能明显感觉到沉浸式所带来的变化:状态栏、导航栏隐藏。
而对于使用实体按键的手机的用户来说,“沉浸式”所带来的变化仅仅是状态栏隐藏,事实上,状态栏隐藏在之前也很常见,各种国产应用启动时都会隐藏状态栏。

出处来自:为什么在国内会有很多用户把 「透明栏」(Translucent Bars)称作 「沉浸式顶栏」?感兴趣的朋友可以去阅读下。

把上面的文字翻译成图片的话,标准的“沉浸式”大致是这个效果。

标准沉浸式.gif

App默认是全屏的,用户可以从顶部或者底部“滑出”状态栏和导航栏,一段时间后状态栏和导航栏会自动消失。

嗯,没错,这个才是标准的“沉浸式”

有了以上的定义,下面这两种常见的效果,只能说改变了状态栏的“样式”(Translucent Bar倒是比较贴近)。但是,他们不是“沉浸式”

非沉浸式.png

接下来,将进入本篇文章的主题,我们会从两个方面去“论沉浸式”

  • 4.4之前,谷歌是如何提供类似“沉浸式”的体验的
  • 如何以最简单的方式实现“状态栏”与App“合二为一”的效果(真个真的不叫沉浸式啦~)

setSystemUiVisibility
4.0之后(3.0也支持,这里暂时只讨论手机平台),官方提供了这个方法,可以改变系统UI的可见性,使用方法如下:

int flag = View.SYSTEM_UI_FLAG_FULLSCREEN;
getWindow().getDecorView().setSystemUiVisibility(flag);

多个值可使用“|”操作符,比如:

int flag = View.SYSTEM_UI_FLAG_FULLSCREEN 
           | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
getWindow().getDecorView().setSystemUiVisibility(flag);

系统提供了很多类似View.SYSTEM_UI_FLAG_xxx的常量,具体有下面几种:

  • SYSTEM_UI_FLAG_FULLSCREEN(4.1+):隐藏状态栏,手指在屏幕顶部往下拖动,状态栏会再次出现且不会消失,另外activity界面会重新调整大小,直观感觉就是activity高度有个变小的过程。
SYSTEM_UI_FLAG_FULLSCREEN.gif
  • SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN(4.1+):配合SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN一起使用,效果使得状态栏出现的时候不会挤压activity高度,状态栏会覆盖在activity之上。
SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN.gif
  • SYSTEM_UI_FLAG_HIDE_NAVIGATION(4.0+)
    :会使得虚拟导航栏隐藏,但同样用户可以从屏幕下边缘“拖出”且不会再次消失,同时activity界面会被挤压。
SYSTEM_UI_FLAG_HIDE_NAVIGATION.gif
  • SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION(4.1+):配合 SYSTEM_UI_FLAG_HIDE_NAVIGATION 一起使用,效果使得导航栏出现的时候不会挤压activity高度,导航栏会覆盖在activity之上。
SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION.gif
  • SYSTEM_UI_FLAG_LAYOUT_STABLE(4.1+):使用以上四个属性,可以达到activity占据屏幕所有空间,同时状态栏和导航栏可以悬浮在activity之上的效果。但是此时activity的内容也会(比如顶部和底部各有一个TextView)状态栏和导航栏之下,当状态栏和导航栏出现的时候,看起来会这样:
1.gif

显然,文字被遮盖了我们是不能接受的,此时我们需要另外一个属性,android:fitsSystemWindows=“true”,这个属性表示系统UI(状态栏、导航栏)可见的时候,会给我们的布局加上padding(paddingTop、paddingBottom)属性,这样内容就不会被盖住了。我们在activity的根布局加上这个属性,效果如下

fitsSystemWindows.gif

我们发现,当状态栏和导航栏出现的时候,内容会被挤压一下,这个体验也不是很理想,那么怎么解决这个问题呢?

其实,我们的内容本就不该占据状态栏和导航栏的位置(游戏一般才会全屏,普通app的状态栏一般都是透明而不是让内容去占据这个空间,后面会介绍怎么实现状态栏“透明效果”),我们需要加上SYSTEM_UI_FLAG_LAYOUT_STABLE这个属性来解决这个问题
,效果如下

SYSTEM_UI_FLAG_LAYOUT_STABLE.gif

以上都是4.1(除了SYSTEM_UI_FLAG_HIDE_NAVIGATION
)的属性,观察之后我们发现,不管是那种属性,状态栏和导航栏总是会“遮挡”activity,为了解决这个问题,4.4引入了“全屏沉浸模式”这个概念。

  • SYSTEM_UI_FLAG_IMMERSIVE(4.4+):这个属性是用来实现“沉浸式”效果的,官方称作“Immersive full-screen mode”

To provide your app with a layout that fills the entire screen, the new SYSTEM_UI_FLAG_IMMERSIVE
flag for setSystemUiVisibility()
(when combined with SYSTEM_UI_FLAG_HIDE_NAVIGATION
) enables a new immersive full-screen mode.

大意是SYSTEM_UI_FLAG_IMMERSIVE和 SYSTEM_UI_FLAG_HIDE_NAVIGATION一起使用,会带来一个全新的“全屏沉浸模式”。我们使用这两个属性看下效果~

immersive1.gif

纳尼?显然和官方描述的占据整个屏幕有点不符合,状态栏一直占据着空间(不知道为何官方这么描述~)。我们再加一个属性SYSTEM_UI_FLAG_FULLSCREEN,我们再来看下效果

immersive2.gif

挤压的效果如果你不满意,加上SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN和SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION,这会让状态栏和导航栏“悬浮”在activity之上,这个前面提到过,不做更多解释,效果如下

immersive3.gif
  • SYSTEM_UI_FLAG_IMMERSIVE_STICKY:和SYSTEM_UI_FLAG_IMMERSIVE相似,它被称作“粘性”的沉浸模式,这个模式会在状态栏和导航栏显示一段时间后,自动隐藏(你可以点击一下屏幕,立即隐藏)。同时需要重点说明的是,这种模式下,状态栏和导航栏出现的时候是“半透明”状态,效果如下
immersive4.gif

我觉得官方要表达的“Immersive full-screen mode”,应该是这个意思才对吧?

慎用“沉浸式”,除非你真的需要这样做。比如做一款游戏或者绘图应用就很合适。

“Translucent Bar”
网上所谓的“沉浸式状态栏”,这个概念官方从未提及过。我看了就大多数文章,最终表达的效果基本是这样

预览.png

为了对比,贴出了不同Android版本的效果(某Q也是如此),其实就是为了让状态栏看上起和我们应用的标题栏(当然,也可能是和整个背景)“合二为一”,不至于和之前一样,看到的是“黑乎乎”的状态栏,网上也有很多第三方库来实现这样的效果,比如:SystemBarTint

其实这个效果4.4以上实现很简单,大概需要用到以下两个属性:

  • windowTranslucentStatus:application的主题加上这个属性,表示状态栏半透明,另外,会使得状态栏会悬浮在activity之上(此时,activity布局会扩展到状态栏底部(Z轴方向)):

    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">                                
        <item name="android:windowTranslucentStatus">true</item>
    </style>
    

为了不遮挡activity内容,需要配合另外一个属性

  • android:fitsSystemWindows:使用这个属性的View,系统会在View顶部添加padding(大小为状态栏高度):

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:fitsSystemWindows="true"
            android:background="@color/colorPrimary">
            <!--嵌套的原因是因为LinearLayout会被加上paddingTop,导致TextView无法居中-->
            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="50dp">
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_centerInParent="true"
                    android:textColor="@android:color/white"
                    android:textSize="20sp"
                    android:text="这是标题"/>
            </RelativeLayout>
        </LinearLayout>
    </LinearLayout>
    

有些应用没有标题,希望将背景和标题融为一体,比如顶部是“轮播”栏:

预览2.png

其实也很简单,布局稍微调整下(android:fitsSystemWindows="true"不需要设置。如果你是对一个ViewGroup设置背景,你可能会用到这个属性,因为你不想ViewGroup的子布局被状态栏“遮盖”):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <ImageView
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:scaleType="centerCrop"
        android:src="@drawable/bg"/>
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="内容区域"/>
</LinearLayout>

总之一个原则是,如果使用以下属性,activity布局会扩展到状态栏

<item name="android:windowTranslucentStatus">true</item>

如果你希望扩展的区域,不被状态栏盖住内容,那就加上

android:fitsSystemWindows="true"

我们注意到,状态栏在4.4-5.0之间的效果是全透明,5.0+是半透明,颜色的差别QQ等App也没有特意去做处理,我也觉得没必要,你觉得呢?

好了,希望本文能帮助你真正理解什么是“沉浸式”

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

推荐阅读更多精彩内容