沉浸式状态栏,你真的太美了!

10,822 阅读7分钟

沉浸式状态栏效果

我们可以看到,沉浸式的状态是非常漂亮的。

沉浸式状态栏是什么

1、透明状态栏

2.在**状态栏**中的位置显示我们自定义的颜色,一般都是**Toolbar**或者**ActionBar**的颜色
或者是**将整体的一张图片**占据到状态栏中。

学习沉浸式状态栏之前,我们学习一下这个属性fitSystemWindows,这个属性对于沉浸式状态栏的实现非常有用。

fitSystemWindows

fitSystemWindow是什么?

设置系统Window的状态,比如Status bar的状态

设置为true:会调整View的padding给系统的窗口**留出**空间,显示Status bar
如果设置为false:不给系统的窗口流出控件,不显示**Status bar**

例子

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    >
    <android.support.v7.widget.Toolbar
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#ff0000"
        />
</RelativeLayout>

显示的效果为:

如果设置为android:fitsSystemWindows="false",布局如下

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="false"
    >
    <android.support.v7.widget.Toolbar
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#ff0000"
        />
</RelativeLayout>

效果图:

思考

为什么没有效果呢?不是说fitSystemWindow会影响Window中Status bar的显示或者影藏吗?

原因

这个属性必须结合透明状态栏才有效果

会自动的给View增加一个值等于状态栏高度PaddingTop

下面我们真正的实现一下

第一步:透明状态栏的实现

这个地方我们提供2种办法

第一种方法:在主题中实现

由于修改状态栏是**4.4以后**才出现的,所以我们创建主题文件的时候要创建**对应**的包
1.创建**values-v19**文件夹,然后创建style.xml文件,定义AppTheme.Main主题
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <style name="AppTheme.Main" 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>
        <item name="android:windowTranslucentStatus">true</item>
    </style>
</resources>

2、然后让MainActivity中继承这样的主题

第二种办法:在代码中控制
package com.example.cdx.test;

import android.os.Build;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.WindowManager;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //>18版本就能进行设置了
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            //透明状态栏
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        }
    }
}

第二步:然后在布局文件中使用TextView

<?xml version="1.0" encoding="utf-8"?>
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:text="Love You"
    android:textSize="20sp"
    android:background="@android:color/holo_blue_bright"
/>

运行效果:

发现文字和状态栏进行重合了。

第三步:增加 android:fitsSystemWindows="true"属性

<?xml version="1.0" encoding="utf-8"?>
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:text="Love You"
    android:textSize="20sp"
    android:background="@android:color/holo_blue_bright"
/>

效果图

请看下面的需求

在Toolbar上使用

上面的例子是在根布局也就是TextView上使用了fitSystemWindow上使用了这android:fitsSystemWindows="true"这个属性,但是在实际的开发中,我们更常见的是这种情况

第一步:创建values-v19文件夹,然后创建style.xml文件

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

    <style name="AppTheme.Main" 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>
        <item name="android:windowTranslucentStatus">true</item>
    </style>
</resources>

第二步:然后让MainActivity继承这个主题

<activity android:name=".MainActivity"
            android:theme="@style/AppTheme.Main"
            >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

第三步:然后我将演示错误的写法,将fitSystemWindow写在跟布局LinearLayout上

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical"
    >

    <android.support.v7.widget.Toolbar
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_blue_light"
        >
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="你好"
            android:layout_gravity="center"
            />
    </android.support.v7.widget.Toolbar>
</LinearLayout>

运行,效果如下:

第四步:我修改第三步出现的问题,将 android:fitsSystemWindows="true"卸载Toolbar上
运行,看看效果,非常的nice

下面我将用代码的方式实现透明状态栏

第一步:在布局文件中引入Toolbar,注意,并没有写上fitSystemWindow

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_blue_light"
        >
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="你好"
            android:layout_gravity="center"
            />
    </android.support.v7.widget.Toolbar>
</LinearLayout>

第二步:在MainActivity中实现状态栏

package com.example.cdx.test;

import android.content.Context;
import android.os.Build;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        ActionBar actionBar = getSupportActionBar();
        if (actionBar!=null){
            actionBar.setDisplayShowTitleEnabled(false);
        }

        //5.0以上系统
        //第一步:设置Window的表示为透明的状态栏标识
        //第二步:获得状态栏的高度
        //第三步:设置Toolbar的padding为状态栏的高度
        //通过上面的3步,实现了沉浸式状态栏
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            setImmerseLayout(toolbar);
        }
    }

    protected void setImmerseLayout(View view) {// view为标题栏
        //当版本是4.4以上
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            Window window = getWindow();
            window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            int statusBarHeight = getStatusBarHeight(this.getBaseContext());
            view.setPadding(0, statusBarHeight, 0, 0);
        }
    }

    public int getStatusBarHeight(Context context) {
        int result = 0;
        int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            result = context.getResources().getDimensionPixelSize(resourceId);
        }
        return result;
    }
}

效果图,非常完美。

另类的实现方式

今天看到项目中实现这种效果的另外的一种方式

看看如何实现的吧?

首先看看这张图,状态栏的颜色由colorPrimaryDark控制

所以

第一步:我们可以在AppTheme中控制colorPrimaryDark的颜色

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@android:color/holo_blue_light</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>

第二步:然后让MainActivity来继承这个主题

第三步:让布局文件中的toolbar的颜色和主题中colorPrimaryDark颜色一致

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical"
    >

    <android.support.v7.widget.Toolbar
        android:id="@+id/login_toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_blue_light"
        app:titleTextColor="@color/skin_toolbar_title" />

</RelativeLayout>

运行,效果如下:

沉浸式状态栏实现方式

第一种方式

注意:第一种方式考虑的不全面,第一种方式看完以后,请继续看第一种方式补充

效果图

实现思路

1、使用NoActionBar的主题Theme.AppCompat.Light.NoActionBar,用来隐藏标题栏

2、使用透明状态栏true,

3、让控件距离顶部有一个padding。

具体实现

1步:设置主题,并且设置透明状态栏

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

    <style name="AppTheme" 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>
        <!--透明状态栏-->
        <item name="android:windowTranslucentStatus">true</item>
    </style>
</resources>

2、然后让Application使用这个主题

android:theme="@style/AppTheme"

3、使用android:fitsSystemWindows="true"让TextView在状态栏里面距离顶部有一个长度为状态栏高度的padding。

注意:android:fitsSystemWindows="true"要设置到TextView控件上,不要设置到LinearLayout控件上。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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=".MainActivity"
    android:orientation="horizontal"
    >

    <TextView
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:text="Hello World!"
        android:background="@color/colorAccent"
        android:fitsSystemWindows="true"
        />

</LinearLayout>

第一种方式的补充:兼容性

背景:

1、在Android4.4以前,我们打开App,总能看到系统的顶部那条黑呼呼的通知栏,这样会使的App看起来比较突兀不美观

2、在**Android4.4以后**,便引入了**Translucent System Bar** 的特性
3、模仿了IOS的一些UI的效果

不完善的地方:

状态栏透明是androd4.4以后的新特性,android4.4以前是没有这个特性的,所以,要对这个特性进行版本区分

androd4.4以下:是一种实现逻辑。

android4.4 - android5.0以下:是一种实现逻辑。

android5.0及以上:是一种实现逻辑。

如何实现:

第一步:设置AppTheme,其中AppTheme为NoActionBar的样式。

<!-- Base application theme. -->
    <style name="AppTheme" 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>

第二步:需要在values,values-v19,values-v21的style.xml中设置不同风格的Theme。

values/style.xml

<style name="ImageTranslucentTheme" parent="AppTheme">
    <!--在Android 4.4之前的版本上运行,直接跟随系统主题-->
</style>

values-v19/style.xml

<style name="ImageTranslucentTheme" parent="AppTheme">
    <!--状态栏是否透明:透明-->
    <item name="android:windowTranslucentStatus">true</item>
     <!--导航栏是否透明:透明-->
    <item name="android:windowTranslucentNavigation">true</item>
</style>

values-v21/style.xml

<style name="ImageTranslucentTheme" parent="AppTheme">
    <!--状态栏是否透明:透明-->
    <item name="android:windowTranslucentStatus">false</item>
    <!--导航栏是否透明:透明-->    
    <item name="android:windowTranslucentNavigation">true</item>
    <!--Android 5.x开始需要把颜色设置透明,否则导航栏会呈现系统默认的浅灰色-->
    <item name="android:statusBarColor">@android:color/transparent</item>
</style>

第一种方式补充:设置状态栏文字颜色

需求:

经常有这样的需求,设置了沉浸式的状态栏,但是产品想要设置状态栏上的文字颜色状态,

我们可以通过设置状态栏为浅色模式,这样状态栏上的文字就为深色了。

如何实现:

第一步:引入工具类

package com.example.myapplication;

import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.os.Build;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class StatusBarUtil {
  /**
   * 修改状态栏为全透明
   */
  public static void transparencyBar(Activity activity) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      Window window = activity.getWindow();
      window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
      window.getDecorView()
          .setSystemUiVisibility(
              View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
      window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
      window.setStatusBarColor(Color.TRANSPARENT);
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
      Window window = activity.getWindow();
      window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
          WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
    }
  }

  /**
   * 状态栏亮色模式,设置状态栏黑色文字、图标,
   * 适配4.4以上版本MIUIV、Flyme和6.0以上版本其他Android
   *
   * @return 1:MIUUI 2:Flyme 3:android6.0
   */
  public static void statusBarLightMode(Activity activity) {
    int result = 0;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
      if (miuiSetStatusBarLightMode(activity, true)) {
        result = 1;
      } else if (flymeSetStatusBarLightMode(activity.getWindow(), true)) {
        result = 2;
      } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        activity.getWindow()
            .getDecorView()
            .setSystemUiVisibility(
                View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
        result = 3;
      }
    }
  }

  /**
   * 已知系统类型时,设置状态栏黑色文字、图标。
   * 适配4.4以上版本MIUIV、Flyme和6.0以上版本其他Android
   *
   * @param type 1:MIUUI 2:Flyme 3:android6.0
   */
  public static void statusBarLightMode(Activity activity, int type) {
    if (type == 1) {
      miuiSetStatusBarLightMode(activity, true);
    } else if (type == 2) {
      flymeSetStatusBarLightMode(activity.getWindow(), true);
    } else if (type == 3) {
      activity.getWindow()
          .getDecorView()
          .setSystemUiVisibility(
              View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
    }
  }

  /**
   * 状态栏暗色模式,清除MIUI、flyme或6.0以上版本状态栏黑色文字、图标
   */
  public static void StatusBarDarkMode(Activity activity, int type) {
    if (type == 1) {
      miuiSetStatusBarLightMode(activity, false);
    } else if (type == 2) {
      flymeSetStatusBarLightMode(activity.getWindow(), false);
    } else if (type == 3) {
      activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
    }
  }

  /**
   * 设置状态栏图标为深色和魅族特定的文字风格
   * 可以用来判断是否为Flyme用户
   *
   * @param window 需要设置的窗口
   * @param dark 是否把状态栏文字及图标颜色设置为深色
   * @return boolean 成功执行返回true
   */
  public static boolean flymeSetStatusBarLightMode(Window window, boolean dark) {
    boolean result = false;
    if (window != null) {
      try {
        WindowManager.LayoutParams lp = window.getAttributes();
        Field darkFlag =
            WindowManager.LayoutParams.class.getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON");
        Field meizuFlags = WindowManager.LayoutParams.class.getDeclaredField("meizuFlags");
        darkFlag.setAccessible(true);
        meizuFlags.setAccessible(true);
        int bit = darkFlag.getInt(null);
        int value = meizuFlags.getInt(lp);
        if (dark) {
          value |= bit;
        } else {
          value &= ~bit;
        }
        meizuFlags.setInt(lp, value);
        window.setAttributes(lp);
        result = true;
      } catch (Exception e) {

      }
    }
    return result;
  }

  /**
   * 需要MIUIV6以上
   *
   * @param dark 是否把状态栏文字及图标颜色设置为深色
   * @return boolean 成功执行返回true
   */
  public static boolean miuiSetStatusBarLightMode(Activity activity, boolean dark) {
    boolean result = false;
    Window window = activity.getWindow();
    if (window != null) {
      Class clazz = window.getClass();
      try {
        int darkModeFlag;
        Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");
        Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");
        darkModeFlag = field.getInt(layoutParams);
        //noinspection unchecked
        Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);
        if (dark) {
          //状态栏透明且黑色字体
          extraFlagField.invoke(window, darkModeFlag, darkModeFlag);
        } else {
          //清除黑色字体
          extraFlagField.invoke(window, 0, darkModeFlag);
        }
        result = true;

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
          //开发版 7.7.13 及以后版本采用了系统API,旧方法无效但不会报错,所以两个方式都要加上
          if (dark) {
            activity.getWindow()
                .getDecorView()
                .setSystemUiVisibility(
                    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
          } else {
            activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
          }
        }
      } catch (Exception e) {

      }
    }
    return result;
  }

  //获取屏幕虚拟键高度
  public static int getVirtualBarHeight(Context context) {
    int vh = 0;
    WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    Display display = windowManager.getDefaultDisplay();
    DisplayMetrics dm = new DisplayMetrics();
    try {
      @SuppressWarnings("rawtypes") Class c = Class.forName("android.view.Display");
      @SuppressWarnings("unchecked") Method method =
          c.getMethod("getRealMetrics", DisplayMetrics.class);
      method.invoke(display, dm);
      vh = dm.heightPixels - display.getHeight();
    } catch (Exception e) {
      e.printStackTrace();
    }
    return vh;
  }

  public static int getStatusBarHeight(Context context) {
    int height = 0;
    try {
      Class c = Class.forName("com.android.internal.R$dimen");
      Field f = c.getField("status_bar_height");
      int id = (int) f.get(null);
      height = context.getResources().getDimensionPixelSize(id);
    } catch (Exception e) {
    }
    return height;
  }

  public static int dip2px(Context context, float dpValue) {
    final float scale = context.getResources().getDisplayMetrics().density;
    return (int) (dpValue * scale + 0.5f);
  }

  public static int px2dip(Context context, float pxValue) {
    final float scale = context.getResources().getDisplayMetrics().density;
    return (int) (pxValue / scale + 0.5f);
  }

  /** 获取手机的密度 */
  public static float getDensity(Context context) {
    DisplayMetrics dm = context.getResources().getDisplayMetrics();
    return dm.density;
  }
}

第二步:然后在setContentView之前设置StatusBarUtil.statusBarLightMode(this);

    package com.example.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        StatusBarUtil.statusBarLightMode(this);
        setContentView(R.layout.activity_main);
    }
}

第二种实现:终极解决办法

首先我们看看状态栏导航栏的几种情况,效果主要有下面的4种:

1、自定义颜色的状态栏和导航栏
2、半透明的状态栏和导航栏
3、完全透明的状态栏和导航栏(其实就是第二种的极限状态,我更喜欢 叫这种为沉浸式状态栏和导航栏)
4、隐藏状态栏和导航栏

如何实现

使用UltimateBar(**终极bar**)库去完成,这个库在Android4.4和Android5.0版本分别做了处理
使他在不同的版本上效果达到了空前的统一,非常的好。
Github上有实现,非常的好。