Android-transulcent-status-bar

Android-transulcent-status-bar总结

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


Github Demo 链接: StatusBarCompat


参考文章:


  1. 由沉浸式状态栏引发的血案

  2. Translucent System Bar 的最佳实践

  3. 该使用 fitsSystemWindows 了!


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



先定义几个名词:


  1. 全屏模式: 左边图所示.

  2. 着色模式: 右边图所示.

  3. ContentView: activity.findViewById(Window.ID_ANDROID_CONTENT) 获取的 View , 即 setContentView 方法所设置的 View, 实质为 FrameLayout.

  4. ContentParent: ContentView 的 parent , 实质为 LinearLayout.

  5. ChildView: ContentView 的第一个子 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 来设置状态栏的颜色.


全屏模式:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 );
}


着色模式:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 .


全屏模式:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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);
      }
}


着色模式:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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.


全屏模式:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 );
}


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


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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.


全屏模式:


1
2
3
4
5
6
7
8
9
10
11
12
13
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 );
}


着色模式:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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);


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


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



方法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开发中文站
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值