最近业务上看到一个设计图挺好看,所以研究了一下透明状态栏,注意不是沉浸式状态栏,在参考了网上的一些资料后,整理出了这篇博客.

Github Demo 链接: StatusBarCompat

参考文章:

  1. 由沉浸式状态栏引发的血案
  2. Translucent System Bar 的最佳实践
  3. 该使用 fitsSystemWindows 了!

首先强调,对于状态栏的处理有两种不同的方式, 这里从Translucent System Bar 的最佳实践直接盗了两张图做对比~.

全屏( ContentView 可以进入状态栏) 非全屏 ( ContentView 与状态栏分离, 状态栏直接着色)

先定义几个名词:

  1. 全屏模式: 左边图所示.
  2. 着色模式: 右边图所示.
  3. ContentViewactivity.findViewById(Window.ID_ANDROID_CONTENT) 获取的 View , 即 setContentView 方法所设置的 View, 实质为 FrameLayout.
  4. ContentParentContentView 的 parent , 实质为 LinearLayout.
  5. ChildViewContentView 的第一个子 View ,即布局文件中的 layout .

再介绍一下相关的函数:

  1. fitsSystemWindows, 该属性可以设置是否为系统 View 预留出空间, 当设置为 true 时,会预留出状态栏的空间.
  2. ContentView, 实质为 ContentFrameLayout, 但是重写了 dispatchFitSystemWindows 方法, 所以对其设置 fitsSystemWindows 无效.
  3. ContentParent, 实质为 FitWindowsLinearLayout, 里面第一个 View 是 ViewStubCompat, 如果主题没有设置 title ,它就不会 inflate .第二个 View 就是 ContentView.

5.0以上的处理:

自5.0引入 Material Design ,状态栏对开发者更加直接,可以直接调用 setStatusBarColor 来设置状态栏的颜色.

全屏模式:

Window window = activity.getWindow();
//设置透明状态栏,这样才能让 ContentView 向上
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); //需要设置这个 flag 才能调用 setStatusBarColor 来设置状态栏颜色
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
//设置状态栏颜色
window.setStatusBarColor(statusColor); ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);
View mChildView = mContentView.getChildAt(0);
if (mChildView != null) {
//注意不是设置 ContentView 的 FitsSystemWindows, 而是设置 ContentView 的第一个子 View . 使其不为系统 View 预留空间.
ViewCompat.setFitsSystemWindows(mChildView, false);
}

着色模式:

Window window = activity.getWindow();
//取消设置透明状态栏,使 ContentView 内容不再覆盖状态栏
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); //需要设置这个 flag 才能调用 setStatusBarColor 来设置状态栏颜色
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
//设置状态栏颜色
window.setStatusBarColor(statusColor); ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);
View mChildView = mContentView.getChildAt(0);
if (mChildView != null) {
//注意不是设置 ContentView 的 FitsSystemWindows, 而是设置 ContentView 的第一个子 View . 预留出系统 View 的空间.
ViewCompat.setFitsSystemWindows(mChildView, true);
}

4.4-5.0的处理:

4.4-5.0因为没有直接的 API 可以调用,需要自己兼容处理,网上的解决方法基本都是创建一下高度为状态栏的 View ,通过设置这个 View 的背景色来模拟状态栏. 这里我尝试了三种方法来兼容处理.

方法1: 向 ContentView 添加假 View , 设置 ChildView 的 marginTop 属性来模拟 fitsSystemWindows .

全屏模式:

Window window = activity.getWindow();
ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT); //首先使 ChildView 不预留空间
View mChildView = mContentView.getChildAt(0);
if (mChildView != null) {
ViewCompat.setFitsSystemWindows(mChildView, false);
} int statusBarHeight = getStatusBarHeight(activity);
//需要设置这个 flag 才能设置状态栏
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
//避免多次调用该方法时,多次移除了 View
if (mChildView != null && mChildView.getLayoutParams() != null && mChildView.getLayoutParams().height == statusBarHeight) {
//移除假的 View.
mContentView.removeView(mChildView);
mChildView = mContentView.getChildAt(0);
}
if (mChildView != null) {
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mChildView.getLayoutParams();
//清除 ChildView 的 marginTop 属性
if (lp != null && lp.topMargin >= statusBarHeight) {
lp.topMargin -= statusBarHeight;
mChildView.setLayoutParams(lp);
}
}

着色模式:

Window window = activity.getWindow();
ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT); //First translucent status bar.
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
int statusBarHeight = getStatusBarHeight(activity); View mChildView = mContentView.getChildAt(0);
if (mChildView != null) {
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mChildView.getLayoutParams();
//如果已经为 ChildView 设置过了 marginTop, 再次调用时直接跳过
if (lp != null && lp.topMargin < statusBarHeight && lp.height != statusBarHeight) {
//不预留系统空间
ViewCompat.setFitsSystemWindows(mChildView, false);
lp.topMargin += statusBarHeight;
mChildView.setLayoutParams(lp);
}
} View statusBarView = mContentView.getChildAt(0);
if (statusBarView != null && statusBarView.getLayoutParams() != null && statusBarView.getLayoutParams().height == statusBarHeight) {
//避免重复调用时多次添加 View
statusBarView.setBackgroundColor(statusColor);
return;
}
statusBarView = new View(activity);
ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight);
statusBarView.setBackgroundColor(statusColor);
//向 ContentView 中添加假 View
mContentView.addView(statusBarView, 0, lp);

方法2: 向 ContentParent 添加假 View ,设置 ContentView 和 ChildView 的 fitsSystemWindows.

全屏模式:

Window window = activity.getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);
ViewGroup mContentParent = (ViewGroup) mContentView.getParent(); View statusBarView = mContentParent.getChildAt(0);
if (statusBarView != null && statusBarView.getLayoutParams() != null && statusBarView.getLayoutParams().height == getStatusBarHeight(activity)) {
//移除假的 View
mContentParent.removeView(statusBarView);
}
//ContentView 不预留空间
if (mContentParent.getChildAt(0) != null) {
ViewCompat.setFitsSystemWindows(mContentParent.getChildAt(0), false);
} //ChildView 不预留空间
View mChildView = mContentView.getChildAt(0);
if (mChildView != null) {
ViewCompat.setFitsSystemWindows(mChildView, false);
}

着色模式(会有一条黑线,无法解决):

Window window = activity.getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);
ViewGroup mContentParent = (ViewGroup) mContentView.getParent(); View statusBarView = mContentParent.getChildAt(0);
if (statusBarView != null && statusBarView.getLayoutParams() != null && statusBarView.getLayoutParams().height == getStatusBarHeight(activity)) {
//避免重复调用时多次添加 View
statusBarView.setBackgroundColor(statusColor);
return;
} //创建一个假的 View, 并添加到 ContentParent
statusBarView = new View(activity);
ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
getStatusBarHeight(activity));
statusBarView.setBackgroundColor(statusColor);
mContentParent.addView(statusBarView, 0, lp); //ChildView 不需要预留系统空间
View mChildView = mContentView.getChildAt(0);
if (mChildView != null) {
ViewCompat.setFitsSystemWindows(mChildView, false);
}

方法3:向 ContentView 添加假 View , 设置 ChildView 的 fitsSystemWindows.

全屏模式:

Window window = activity.getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);
View statusBarView = mContentView.getChildAt(0);
//移除假的 View
if (statusBarView != null && statusBarView.getLayoutParams() != null && statusBarView.getLayoutParams().height == getStatusBarHeight(activity)) {
mContentView.removeView(statusBarView);
}
//不预留空间
if (mContentView.getChildAt(0) != null) {
ViewCompat.setFitsSystemWindows(mContentView.getChildAt(0), false);
}

着色模式:

Window window = activity.getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);
int statusBarHeight = getStatusBarHeight(activity); View mTopView = mContentView.getChildAt(0);
if (mTopView != null && mTopView.getLayoutParams() != null && mTopView.getLayoutParams().height == statusBarHeight) {
//避免重复添加 View
mTopView.setBackgroundColor(statusColor);
return;
}
//使 ChildView 预留空间
if (mTopView != null) {
ViewCompat.setFitsSystemWindows(mTopView, true);
} //添加假 View
mTopView = new View(activity);
ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight);
mTopView.setBackgroundColor(statusColor);
mContentView.addView(mTopView, 0, lp);

其实全屏模式在三种模式下实现都是一样的,主要是着色模式实现不同.

public static int getStatusHeight(Activity activity) {
int statusHeight = 0;
Rect localRect = new Rect();
activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(localRect);
statusHeight = localRect.top;
if (0 == statusHeight) {
Class<?> localClass;
try {
localClass = Class.forName("com.android.internal.R$dimen");
Object localObject = localClass.newInstance();
int i5 = Integer.parseInt(localClass.getField("status_bar_height").get(localObject).toString());
statusHeight = activity.getResources().getDimensionPixelSize(i5);
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException | IllegalArgumentException | SecurityException | NoSuchFieldException e) {
e.printStackTrace();
}
}
return statusHeight;
}

  

对比一下三种着色模式实现的方式:

  方法1 方法2 方法3
原理 向 ContentView 中添加假 View, 然后利用 ChildView的 marginTop 属性来模拟 fitsSystemWindows ,主要是通过修改 marginTop 的值可以在全屏模式和着色模式之间切换. 因为 ParentView 的实质是一个 LinearLayout , 可以再其顶部添加 View . 向 ContentView 中添加假 View, 然后利用 ChildView 的 fitsSystemWindows 属性来控制位置, 但是实现缺陷就是不能随时切换两种模式.
缺陷 改变了 ChildView 的 marginTop 值 着色模式下,会像由沉浸式状态栏引发的血案中一样出现一条黑线 不能在不重启 Activity 的情况下切换模式.
对应 Github demo 中代码 StatusBarCompat类 StatusBarCompat1类 StatusBarCompat2 类

总结

  • StatusBarCompat2 主要问题不能切换.
  • StatusBarCompat1 在4.4上会有一条黑线, 如果可以解决我觉得这是最靠谱的解决方法.
  • StatusBarCompat 类算是我最后给出的解决方案吧, 目前使用效果比较完善.推荐使用
    • 用户可以随时在同一个 Activity 中切换不同的状态栏模式.
    • 就算子 View 重写了 dispatchFitSystemWindows 也不会有影响.

Android-transulcent-status-bar的更多相关文章

  1. the status bar issue of react-native Modal on Android ( RN v0.57.0)

    Problem: When use Modal in react-native, the status bar is not included if you make a full-screen ma ...

  2. [Android] 获取系统顶部状态栏(Status Bar)与底部导航栏(Navigation Bar)的高度

    Android一些设备都有上下两条bar,我们可以获取这些bar的信息.下面放上获取高度的代码.代码注释和其他方法有空再放. 原文地址请保留http://www.cnblogs.com/rossone ...

  3. 与Status Bar和Navigation Bar相关的一些东西

    Android Navigation Bar Status Bar   与StatusBar和NavigationBar相关的东西有两种,一是控制它们的显示与隐藏,二是控制它们的透明与否及背景. 在2 ...

  4. [工作积累] Android: Hide Navigation bar 隐藏导航条

    https://developer.android.com/training/system-ui/navigation.html View decorView = getWindow().getDec ...

  5. Android hide Navigation bar

    最近一个app需要隐藏Navigation bar导航栏. 参考文档 http://blog.csdn.net/zwlove5280/article/details/52823128 http://j ...

  6. Material Design 之 定义状态栏(Status Bar)的颜色

    Hey,好久不见.今天遇到一个问题,想要把Status Bar 和 Tool Bar的颜色弄成一样的,或者是类似的,例如Material Design: 图中Status Bar颜色比Tool Bar ...

  7. 怎样将DrawerLayout显示在ActionBar/Toolbar和status bar之间

    控制status bar utm_source=tuicool#toc_1" style="color:rgb(0,0,0); text-decoration:none; line ...

  8. status bar、navigationBar、tableView吸顶view设置

    1. 隐藏navigationBar self.navigationController.navigationBar.hidden = YES; 2. status bar设置 -(void)view ...

  9. iOS7下隐藏status bar的详细研究

    info.plist文件中,View controller-based status bar appearance项设为YES,则View controller对status bar的设置优先级高于a ...

  10. Java基础之扩展GUI——添加状态栏(Sketcher 1 with a status bar)

    控制台程序. 为了显示各个应用程序参数的状态,并且将各个参数显示在各自的面板中,在应用程序窗口的底部添加状态栏是常见且非常方便的方式. 定义状态栏时没有Swing类可用,所以必须自己建立StatusB ...

随机推荐

  1. Windows坐标系

    .逻辑坐标 逻辑坐标是独立于设备的,它与设备点的大小无关.使用逻辑单位,是实现"所见即所得"的基础.当程序员在调用一个画线的GDI函数LineTo,画出25.4mm(1英寸) 长的 ...

  2. Mongodb增加权限管理

     前言: 随着列式存储理念的成熟,越来越多的开发者开始接纳mongodb,hbase这类大储存的分布式列式数据库.特别是mongodb的这种快速搭建,快速使用特点,使其得到更多人的青睐.本人主要通过官 ...

  3. Web前端开发

    由于互联网的各种兴起,网页开发似乎也火了,催生了github上各种js的轮子,各种重复,各种框架和库,什么Jquery,bootstrap等等.面对这么多框架和库我们在工程上该如何取舍(trade-o ...

  4. vbox端口转发

    端口转发:setting->network->adapter:attached to NAT.port forwarding rules->name    protocol     ...

  5. java模拟http post

    我们将使用java.net.URLConnection来完成一次post请求,假设要post数据到http://localhost:8080/doSome上: URLConnection urlCon ...

  6. 广州Uber优步司机奖励政策(12月28日到1月3日)

    滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...

  7. java--文件过滤器和简单系统交互

    一.文件过滤器 /** * @Title: getFileByFilter * @Description: 根据正则rege获取给定路径及其子路径下的文件名(注意递归的深度不要太大) * @param ...

  8. ORACLE表空间

    在ORACLE数据库中,所有数据从逻辑结构上看都是存放在表空间当中,当然表空间下还有段.区.块等逻辑结构.从物理结构上看是放在数据文件中.一个表空间可由多个数据文件组成. 如下图所示,一个数据库由对应 ...

  9. java笔记5之逻辑运算符以及&&与&的区别

    1 &逻辑与:有false则false.         |逻辑或:有true则true.         ^逻辑异或:相同为false,不同为true.            举例:情侣关系 ...

  10. asdasd

    adasdasd asdasd asdasd asd