首页 > 技术文章 > Android开发 系统栏开发(状态栏与导航栏)

guanxinjing 2020-06-03 18:53 原文

前言

  很多UI设计都要求修改状态栏的一些颜色、字体颜色、沉浸式等等效果,这篇博客就重点整理所有状态栏开发技术点。

  转载请注明来源:https://www.cnblogs.com/guanxinjing/p/13039486.html

关于国内手机厂商的适配问题

  我目前已经不太关心适配问题了,之前的Android版本因为google的问题导致状态栏上严重跟不上时代,各种效果无法实现,导致各个厂商都自己实现状态栏碎片化很严重,让兼容问题无比复杂,但是在Android8.0后。google已经完善了状态栏最后一块短板,所以大部分厂商已经放弃了自己造轮子的行为,加入了google原生的实现。且目前国内的设备普遍都是Android8.0以上了。之前的各色状态栏兼容工具类已经意义不大了,所以回归到了正道上,学习google原生折腾状态栏的方法。

改变colorPrimaryDark颜色, 从而改变状态栏颜色

 在app创建后在styles文件里的AppTheme(这个是默认主题,如果你修改为了其他你自定义的主题,以你的主题为标准)主题下colorPrimaryDark属性,它是作用于状态栏的颜色值,参考下图了解每一个属性作用域:

 

 

改变colorPrimaryDark从而改变全局App状态栏颜色

修改colorPrimaryDark改变状态栏颜色,这是最简单的方式,可以把全局App的每个页面的状态栏颜色都修改成指定颜色,代码如下:

styles.xml

这里将状态栏修改成黄色

 

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">#FF9800</item> <!--修改的黄色颜色值,这里只是举例。实际项目还是把颜色值老老实实地放在color文件里-->
        <item name="colorAccent">@color/colorAccent</item>
    </style>

注意,这个主题要设置成app全局主题,在AndroidManifest里设置。

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yt.modeldemo">

    <application
        android:allowBackup="true"
        android:hardwareAccelerated="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"> <!--在这里设置全局主题-->

效果图:

 

 

修改colorPrimaryDark,改变某一个Activity的状态栏颜色

一个app全局只使用一种状态栏颜色,这只是一个美好的愿望。 实际项目十分蛋疼,各种activity都需要各种颜色的状态栏。我们可以创建一个新的主题改变colorPrimaryDark颜色后,给这个activity使用。代码如下:

styles.xml

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">#FF9800</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

    <style name="MainActivityTheme" parent="AppTheme"> <!--其他属性的颜色,我们依然需要使用全局AppTheme,所以这里直接引用它-->
        <item name="colorPrimaryDark">#2196F3</item> <!--修改这MainActivity需要使用的状态栏颜色-->
    </style>

AndroidManifest.xml

找到需要设置的activity,将主题设置给它。

        <activity android:name=".MainActivity"
            android:theme="@style/MainActivityTheme"> <!--设置这个activity的主题-->
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

在activity里:

当然除了在AndroidManifest.xml里设置,我们还可以直接在代码了里直接设置这个主题。请注意!代码里设置主题一定要在 setContentView(R.layout.activity_main); 方法前面。(喜欢搞BaseActivity封装setContentView的同学注意了!你们这就是脱裤子放屁多此一举,反而在会影响别人设置setTheme,深刻了解什么是过度封装,什么代码统一一个地方修改全局,那也得看看是什么形式的封装,google直接在创建Activity的时候都帮你写好了,你这么厉害怎么不去google?)

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setTheme(R.style.MainActivityTheme);//设置主题,一定要在setContentView前面
        setContentView(R.layout.activity_main);
    }

效果图:

 

 

 

setStatusBarColor方法改变状态栏颜色

此方法是Android 5.0 后提供的

代码:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getWindow().setStatusBarColor(Color.RED);
//略....

效果图:

 

切换状态栏字体与图标颜色模式

说是切换颜色,其实Android只有2种模式,一种是深色模式,一种是高亮模式。Android的状态栏字体与小图标颜色是不允许自定义颜色的。提供这两种模式是希望用户可以在深色与亮色模式下都能看清楚状态栏内容。

白底黑字的高亮模式

代码:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().setStatusBarColor(Color.WHITE);/*设置状态栏背景颜色为白色*/
        getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);//白低黑字的高亮模式
        setContentView(R.layout.activity_main);
        //略....

效果图:

默认的黑低白字的深色模式

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().setStatusBarColor(Color.BLACK);/*设置状态栏背景颜色为黑色*/
        getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);//STABLE为稳固的意思,其实就是默认模式
        setContentView(R.layout.activity_main);
        //略....

效果图:

在Api30之后上面方法提示过时,提示使用WindowInsetsController替代

       val windowController = ViewCompat.getWindowInsetsController(mBinding.root)
        windowController?.isAppearanceLightStatusBars = false //状态栏文字白色
        windowController?.isAppearanceLightStatusBars = true //状态栏文字黑色

 

在style Theme里设置

这个需要API 23 (Android 6.0)

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

隐藏与显示状态栏

有两种方式

第一种,设置Window flag形式,让window全屏显示,达到隐藏状态栏的目的

这种方式是Android3.0以前隐藏状态栏的方式,目前google应该不希望你用这种方式控制状态栏,这只是控制window时达到的一种效果。请注意,使用这个方式可能会影响输入盘的自动调整功能,所以非必要不应该使用这种方式隐藏状态栏。

// 隐藏状态栏
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
// 显示状态栏
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);

隐藏效果图:

第二种,控制系统UI setSystemUiVisibility 隐藏或者显示状态栏

这种应该是google的推荐方式。是Android4.1后实现的方式

        getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN);//隐藏状态栏
        getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);//显示状态栏
        getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);//状态栏图标低调模式 

低调模式效果图:

自保留电量并且淡化电量图标,其他图标全部隐藏

上面使用setSystemUiVisibility 在在Api30之后提示过时,使用WindowInsetsController 替代

    fun show(){
        val windowController = ViewCompat.getWindowInsetsController(mBinding.root)
        windowController?.show(WindowInsetsCompat.Type.statusBars())
    }

    fun hide(){
        val windowController = ViewCompat.getWindowInsetsController(mBinding.root)
        windowController?.hide(WindowInsetsCompat.Type.statusBars())
    }

沉浸式状态栏

上面只改改状态栏的颜色,有时候也无法满足UI设计师们希望掌握状态栏的野心。有时候会提供一张图片,希望让你将图片成为状态栏的背景配合App当前主题,这种状态栏就是沉浸式状态栏。这部分可能要涉及到一些标题栏的知识。

因为沉浸式状态栏栏一般就包含了自定义标题栏,因为我们需要自定义一个能设置背景图片的标题栏(或者你干脆没有标题栏),所以需要新增或者修改创建一个没有标题栏的主题:

隐藏标题栏的部分可以参考我另一篇博客:https://www.cnblogs.com/guanxinjing/p/13042584.html

styles.xml

    <style name="NotTitleAppTheme" parent="AppTheme">
        <item name="windowNoTitle">true</item>
    </style>

将这个主题导入,需要实现沉浸式状态栏的Activity

AndroidManifest.xml

        <activity android:name=".MainActivity"
            android:theme="@style/NotTitleAppTheme"> <!--设置这个没有标题栏的主题-->
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

在这个Activity的布局里添加,我们自己的Toolbar标题栏,如果你没有需要标题栏就可以直接放一个ImageView进去

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
    tools:context=".MainActivity">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@mipmap/ic_image"
        android:fitsSystemWindows="true"
        app:layout_constraintTop_toTopOf="parent"
        app:title="标题"
        app:titleTextColor="@android:color/white">

    </androidx.appcompat.widget.Toolbar>

    <Button
        android:id="@+id/add_count_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Hello world"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/toolbar" />
    
</androidx.constraintlayout.widget.ConstraintLayout>

请注意,这里使用了一个 android:fitsSystemWindows="true"  属性,如果是你的是一张图片的ImageView也需要添加,这样它会自动帮我们在标题栏里增加状态栏的高度。

在Activity的代码里设置状态栏与添加Toolbar

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);/*让布局可以全屏,延展到状态栏里*/
        getWindow().setStatusBarColor(Color.TRANSPARENT);/*透明状态栏*/
        
        //略...

请注意,这里设置的 SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 属性与上面的状态栏隐藏是不一样的,这里只是让我们的布局可以延展到状态栏里,让我们布局成为状态栏的一部分,所以叫Layout fullscreen 布局全屏。

效果图:

取消沉浸式

getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);//或者设置SYSTEM_UI_FLAG_LAYOUT_STABLE

在沉浸式状态栏下修改字体模式

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().setStatusBarColor(Color.TRANSPARENT);
        /**
         * 在SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 布局全局情况下 在增加 SYSTEM_UI_FLAG_LAYOUT_STABLE 高亮模式 或者 SYSTEM_UI_FLAG_LAYOUT_STABLE 默认的深色模式
         */
        getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
        setContentView(R.layout.activity_main);
        //略....

 

获取状态栏高度

    /**
     * 获取状态栏高度
     * @param context
     * @return
     */
    public static int getStatusBarHeight(Context context) {
        Resources resources = context.getResources();
        int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
        int height = resources.getDimensionPixelSize(resourceId);
        return height;
    }

导航栏隐藏与显示

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        /**
         * SYSTEM_UI_FLAG_VISIBLE——显示状态栏和导航栏
         *
         * SYSTEM_UI_FLAG_LOW_PROFILE——此模式下,状态栏的图标可能是暗的
         *
         * SYSTEM_UI_FLAG_HIDE_NAVIGATION——隐藏导航栏
         *
         * SYSTEM_UI_FLAG_FULLSCREEN——全屏,隐藏状态栏和导航栏
         *
         * SYSTEM_UI_FLAG_LAYOUT_STABLE 全屏显示尺寸不变
         *
         * SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
         *
         * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN——全屏,隐藏导航栏,状态栏浮在布局上。
         *
         * SYSTEM_UI_FLAG_IMMERSIVE——沉浸式:半透明的状态栏和导航栏
         *
         * SYSTEM_UI_FLAG_IMMERSIVE_STICKY——粘性沉浸式
         */

        /**
         * ==============第一种设置方法 此方法为永久性的====================
         */
        /**
         * 隐藏导航栏与状态栏,将导航栏拉起后会很快速的隐藏导航栏
         */
        WindowManager.LayoutParams params = getWindow().getAttributes();
        params.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION|View.SYSTEM_UI_FLAG_IMMERSIVE;
        getWindow().setAttributes(params);

        /**
         * 隐藏导航栏,但是点击屏幕后会显示导航栏
         */
        WindowManager.LayoutParams params = getWindow().getAttributes();
        params.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
        getWindow().setAttributes(params);

        WindowManager.LayoutParams params = getWindow().getAttributes();
        params.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
                | View.SYSTEM_UI_FLAG_IMMERSIVE
                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
        getWindow().setAttributes(params);

        /**
         * ====================第二种设置方法 此方法为临时性的=========================
         */

        /**
         * 临时性的设置导航栏。跳转其他页面后会导航栏自动恢复,在注解里也有说明,注解的部分如下:
         * 请求更改状态栏或其他屏幕窗口装饰的可见性。此方法用于将设备上的 UI 置于临时模式
         */
        getWindow().getDecorView().setSystemUiVisibility(
                View.SYSTEM_UI_FLAG_FULLSCREEN
                        | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                        | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
                        | View.SYSTEM_UI_FLAG_IMMERSIVE
                        | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);

    }

getWindow().setAttributes() 与 getWindow().getDecorView().setSystemUiVisibility()的区别

getWindow().setAttributes(params) 设置方法 此方法为当前永久性改变当前app的系统栏状态

        WindowManager.LayoutParams params = getWindow().getAttributes();
        params.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION|View.SYSTEM_UI_FLAG_IMMERSIVE;
        getWindow().setAttributes(params);

getWindow().getDecorView().setSystemUiVisibility()设置方法 此方法为临时性的,跳转其他页面后会导航栏自动恢复。这种方法更适合在视频播放器中

在官方注解里也有这段话的说明 :

This method is used to put the over device UI into temporary modes where the user's attention is focused more on the application content

此方法用于将设备上的 UI 置于临时模式,让用户的注意力更多地集中在应用程序内容上

    getWindow().getDecorView().setSystemUiVisibility(
                View.SYSTEM_UI_FLAG_FULLSCREEN
                        | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                        | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
                        | View.SYSTEM_UI_FLAG_IMMERSIVE
                        | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);

 

 

End

推荐阅读