×

Android 状态栏操作,你想知道的都在这里了

96
l_genius
2017.02.24 10:58* 字数 3001

原文链接:https://gold.xitu.io/post/58ad9ca70ce463006b24f134?utm_source=gold_browser_extension
一直以来,iOS 设备上状态栏背景色和图标文字颜色的灵活可变性始终受到设计人员的青睐,有意地恰当地融入到 App 的各种界面设计当中,更好地提升用户体验。

由于系统的限制,在老版本的安卓系统中,Android App 无法做到这些,产生一些设计上的遗憾。幸运的是,自 4.4 版本(API 19)以后,Android 系统开始支持状态栏的定制,并被纳入 Android 设计规范当中,Android App 在设计上迈出了重要的一步。

“沉浸式状态栏” VS “透明状态栏”?

一般来说,Android 默认的状态栏样式表现为黑底白字,如果我们应用的标题栏背景色也为黑色,那就能与状态栏很好地衔接在一起,体验极佳。反之,如果为其他的颜色,整个界面的呈现效果就会大打折扣。
幸运的是,Android 4.4 版本开始,系统提供了相应的 API,支持状态栏全透明化,界面 Content View 可以延伸到状态栏上,填充状态栏背景色。而在 Android 5.0 版本开始,系统在此基础上做了进一步优化和规范,能够实现动态改变状态栏背景色,在透明度上默认呈现为半透明化,可定制化程度更高。
在此基础上,最终要做到我们的应用呈现在 Android 各个系统版本上的效果如图所示:

Status Bar
Status Bar

关于 Android 4.4 版本开始的状态栏变化,许多人喜欢称之为“沉浸式状态栏”,但从系统提供的 API 命名上可以看出,核心词汇为 “Translucent”,故准确来讲,这种效果又应该称之为“透明状态栏”。知乎上对于这两种叫法也颇有争议,具体内容可参考话题:为什么在国内会有很多用户把「透明栏」(Translucent Bars)称作 「沉浸式顶栏」?。可能对于设计师而言,沉浸式还是透明式的称呼有所区别,但对于广大开发者而言,无足轻重,我们所关注的应该是如何实现这种效果,并能够很好的兼容到各个版本中。

相关 API 介绍

一般来说,目前在 Android 项目中我们都会使用 Toolbar 替代 ActionBar 来实现导航栏,除此之外,要实现透明状态栏效果,还需要了解两个相关 API,下面逐一介绍一下:

一:

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

也可以在代码中实现(据说,在代码中实现兼容性更好,style 资源中设置的方式在某些国产手机厂商定制的系统中存在一些问题):

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    WindowManager.LayoutParams localLayoutParams = getWindow().getAttributes();
    local LayoutParams.flags = (WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS | localLayoutParams.flags);
}

顾名思义,该属性能够实现透明状态栏效果,是在 Android 4.4 版本引入的,也就是兼容至 API 19 及以上版本。使用该属性设置主题后,内容布局向上延伸至状态栏,并且在不同版本的系统中呈现效果也有所区别,如图所示:

显然,在 API 19 及更高版本上,Toolbar 内容延伸至状态栏上去了,出现重叠问题,此时,就需要使用到另一个属性了。
二:

android:fitsSystemWindows="true"

Boolean internal attribute to adjust view layout based on system windows such as the status bar. If true, adjusts the padding of this view to leave space for the system windows. Will only take effect if this view is in a non-embedded activity.

用在 layout 布局文件中。官方文档给出了很明确的介绍,大致是说能够将使用该属性的视图与系统窗口(如状态栏)保持一定的 padding 间距。所以如果我们在 toolbar 中设置了该属性,就能够解决<item name="android:windowTranslucentStatus">true</item> 配置带来的视图延伸问题,使呈现效果达到文章开始所示图中的效果。

使用案例分析

res/values/styles 文件中定义基础主题样式:

    <style name="BaseTheme" parent="Theme.AppCompat.Light.NoActionBar"/>
    <style name="AppTheme" parent="BaseTheme">
    </style>

res/values-v19/styles 文件中定义兼容主题样式:

    <style name="AppTheme" parent="BaseTheme">
        <item name="android:windowTranslucentStatus">true</item>
    </style>

然后在 AndroidManifest.xml 文件中使用全局主题样式:

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="Samples"
        android:supportsRtl="true"
        android:name=".MyApplication"
        android:theme="@style/AppTheme">

        ......

新建一个 layout 布局文件,单独定义 toolbar 内容,在应用中的其他 Activity 界面布局中使用 include 标签潜入引用:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/tb_toolbar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:minHeight="?actionBarSize"
    android:background="@color/colorPrimary"
    android:fitsSystemWindows="true"
    app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
    app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
    app:title="@string/app_name"
    app:titleTextColor="@android:color/white">

</android.support.v7.widget.Toolbar>

这里使用android:fitsSystemWindows="true" 属性解决内容试图向上延伸的问题。实际上,也可以使用 android:paddingTop="@dimen/toolbar_padding_top"的方式解决,toolbar_padding_top 间距为状态栏高度,在大多数机器上状态栏高度为 25dp,当然也可以通过代码动态获取状态栏高度并设置到 Toolbar 的 paddingTop 属性上。需要注意的是,这里要做兼容判断,比如在 res/values/dimens.xml 中定义toolbar_padding_top 高度为 0dp,在 res/values-v19/dimens.xml 中为 25dp,确保兼容 Android 4.4 以下版本。

基本上,做到这些就能够实现文章开头处图中的效果。值得注意的是,有时候如果想在 Android 5.0 及以上版本的系统中也做到全透明效果,或者说状态栏与导航栏的颜色一致,还可以做进一步兼容处理,毕竟自 5.0 版本开始,系统对于状态栏背景色的定制提供了更好的 API。如 res/values-v21/styles.xml 中定义:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <style name="AppTheme" parent="BaseTheme">
        <item name="android:colorPrimary">@color/colorPrimary</item>
        <item name="android:colorPrimaryDark">@color/colorPrimary</item>
        <item name="android:colorAccent">@color/colorAccent</item>
    </style>

</resources>

说明一点,使用这种处理方式后,5.0 系统中应用的状态栏背景色可随意定制,同时与使用 <item name="android:windowTranslucentStatus">true</item> 样式有所不同的是,该处理方式不会引起内容视图的向上延伸,所以不需要在 layout 布局文件中额外添加 android:fitsSystemWindows="true" 属性。当然,添加了也无所谓,毕竟还要兼容 4.4 到 5.0 之间的版本。

状态栏白底黑字

前面我们说过,虽然说 4.4 版本开始,可以实现透明状态栏效果,也就是可以通过各种手段实现修改状态栏背景色,但是状态栏图标和文字的颜色默认为白色,这个是无法像 iOS 系统那样,根据应用的整体色调动态修改。如果恰好 Toolbar 的背景色为白色,为了保持一致,将状态栏背景色调为白色的话,就会与状态栏的白色内容发生冲突,导致其内容无法凸显,这个体验肯定无法被用户接受。
所以,遇见这种 Toolbar 或者说导航栏背景色为白色的情况,一般有两种处理方式:第一种,不修改状态栏背景色,通常默认为黑色背景白色内容;第二种,修改状态栏背景色为淡黑色,这样既能显示状态栏内容,又能与白色导航栏弱显衔接,比如支付宝 App 就是这么做的:

Alipay Status Bar
Alipay Status Bar

像上图这种处理方式较黑色状态栏来说,相对缓和一些,那能不能做到修改状态栏内容的颜色呢,比如白底黑字?大家知道,Android 系统是开源的,国内的各家手机厂商都做了一些自己的定制,像部分厂商定制的系统就提供了相应的 API 供开发人员做适配工作。比如,部分厂商就提供了相应的 API 来修改状态栏内容颜色,实现状态栏白底黑字效果,如图:

具体做法就是,在代码中判断系统类型,与提供修改状态内容颜色的系统匹配,使用其特定的 API 操作即可。目前开放这种定制 API 的系统已知有 MIUI 和 Flyme 系统,具体实现代码可参考:

其实,在 Android 6.0(API 23)及更高的版本上,系统也开始提供了对应的 API 来实现浅色调背景的状态栏效果,可将状态栏图标和文字内容改为黑色样式,实现方式为:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 
getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN|View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); 
getWindow().setStatusBarColor(Color.TRANSPARENT);
 }

但是不知国内厂商在定制系统时是否对此有做处理,能否兼容这个 API ?不过,貌似微博 App 是这么做的,大家感兴趣地不妨一试。

注意事项

除了上述 Toolbar 与 状态栏在背景色上的衔接,App 中常见还有这样一种设计,以微博个人主页为例,如图所示:

页面顶部的图片内容延伸至状态栏中,这种做法其实就是单独使用<item name="android:windowTranslucentStatus">true</item>
样式,不在 layout 布局文件中添加android:fitsSystemWindows="true"
属性即可。
还有一点,通常我们会在资源文件中定义不同版本的主题样式,再在 <application>
标签中统一设置,然后所有的 Activity 都能使用这个主题样式。但存在这样一种情况,Activity 由不同的 Fragment 组成,然后不同 Fragment 在状态栏的呈现上有所不同,比如有的 Fragment 顶部使用 Toolbar 与状态栏衔接,有的顶部直接使用图片延伸至状态栏上甚至不会用到 Toolbar,如图所示:

由于 Fragment 是无法像 Activity 那样在 AndroidManifest.xml 中单独设置主题样式的,所以这里可以这样做:宿主 Activity 还是使用透明样式,以满足图片延伸的 Fragment 页面效果,其他使用 Toolbar 的 Fragment,在其 Layout 布局文件顶部单独定义一个 View,让其延伸至状态栏,然后在代码中根据不同版本系统设置其高度,4.4 版本以下设置该 View 高度为0,4.4 及以上版本设置为设备状态栏高度,通过代码获取状态高度的方式如下:

  /**
     * 获取状态栏高度
     * @param activity
     * @return
     */
    public static int getStatusBarHeight(Activity activity){
        Rect rect = new Rect();
        activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
        return rect.top==0 ? 60 : rect.top;
    }

最后再补充一点,大家知道,长按 Toolbar 中 Menu Item 时会显示一个 Toast 提示,内容来自 Item 定义时对应的 title 属性,通常显示如图所示:

但是,如果你误将 fitsSystemWindows
属性设置在了 style 样式文件中,比如:

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

将会导致下图效果:

可见,Menu Options Item 长按时弹出的 Toast 样式没了内容间距,显然很丑。所以,记得将该属性设置在正确的布局文件中,不要设置成全局的。

相关拓展

以上便是有关 Android 4.4 开始的状态栏背景色相关知识,可以看出 4.4 、5.0 、6.0 版本作为三个分水岭,根据需要做好相关适配工作即可。还有一种通过 setSystemUiVisibility()
方法设置状态栏的方式,还能实现状态栏的显示与隐藏交互效果,具体可参考这篇文章:
http://blog.csdn.net/guolin_blog/article/details/51763825

GitHub 上对于 Android 4.4 版本开始的状态栏背景色的处理有一个开源库,感兴趣地朋友也可借鉴参考一番,地址如下:
https://github.com/jgilfelt/SystemBarTint

有关 Toolbar 替换 ActionBar 的使用,可以我之前总结的一篇文章,地址为:
http://yifeng.studio/2016/10/12/android-toolbar/

随笔
Web note ad 1