Android-transulcent-status-bar
最近业务上看到一个设计图挺好看,所以研究了一下透明状态栏,注意不是沉浸式状态栏,在参考了网上的一些资料后,整理出了这篇博客.主要适用场景为一个
Activity
多个tab
在不同状态栏模式下切换.
Github Demo 链接: StatusBarCompat
参考文章:
首先强调,对于状态栏的处理有两种不同的方式, 这里从Translucent System Bar 的最佳实践直接盗了两张图做对比~.
全屏( ContentView 可以进入状态栏) | 非全屏 ( ContentView 与状态栏分离, 状态栏直接着色) |
---|---|
先定义几个名词:
- 全屏模式: 左边图所示.
- 着色模式: 右边图所示.
- ContentView:
activity.findViewById(Window.ID_ANDROID_CONTENT)
获取的 View , 即setContentView
方法所设置的 View, 实质为FrameLayout
. - ContentParent:
ContentView
的 parent , 实质为LinearLayout
. - ChildView:
ContentView
的第一个子 View ,即布局文件中的 root layout .
再介绍一下相关的函数:
fitsSystemWindows
, 该属性可以设置是否为系统 View 预留出空间, 当设置为 true 时,会预留出状态栏的空间.ContentView
, 实质为ContentFrameLayout
, 但是重写了dispatchFitSystemWindows
方法, 所以对其设置fitsSystemWindows
无效.ContentParent
, 实质为FitWindowsLinearLayout
, 里面第一个 View 是ViewStubCompat
, 如果主题没有设置 title ,它就不会 inflate .第二个 View 就是ContentView
.FLAG_TRANSLUCENT_STATUS
, 通过这个 Flag 可以设置状态栏透明(即没有状态栏).FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
, 5.0以上引入的 Flag, 通过这个 Flag 可以设置状态栏颜色.setStatusBarColor()
, 5.0以上直接设置状态栏的方法,但是调用时必须设置FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
且不能设置FLAG_TRANSLUCENT_STATUS
.如果状态栏颜色是不透明的,设置SYSTEM_UI_FLAG_LAYOUT_STABLE
和SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
可以取消5.0上的状态栏阴影.requestApplyInsets()
, 当窗口(Window)大小改变了,通知 View 去消费窗口的改变.
5.0以上的处理:
自5.0引入 Material Design ,状态栏对开发者更加直接,可以直接调用
setStatusBarColor
来设置状态栏的颜色.
全屏模式:
Window window = activity.getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
if (hideStatusBarBackground) {
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.setStatusBarColor(Color.TRANSPARENT);
//隐藏状态栏的阴影window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
} else {
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
}
ViewGroup mContentView = (ViewGroup) window.findViewById(Window.ID_ANDROID_CONTENT);
View mChildView = mContentView.getChildAt(0);
if (mChildView != null) {
ViewCompat.setOnApplyWindowInsetsListener(mChildView, new OnApplyWindowInsetsListener() {
@Override
public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
return insets;
}
});
ViewCompat.setFitsSystemWindows(mChildView, false);
ViewCompat.requestApplyInsets(mChildView);
}
着色模式:
Window window = activity.getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(statusColor);
//清除全屏模式下的 Flag
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
ViewGroup mContentView = (ViewGroup) window.findViewById(Window.ID_ANDROID_CONTENT);
View mChildView = mContentView.getChildAt(0);
if (mChildView != null) {
ViewCompat.setOnApplyWindowInsetsListener(mChildView, new OnApplyWindowInsetsListener() {
@Override
public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
return insets;
}
});
ViewCompat.setFitsSystemWindows(mChildView, true);
ViewCompat.requestApplyInsets(mChildView);
}
4.4-5.0的处理:
4.4-5.0因为没有直接的 API 可以调用,需要自己兼容处理.参考了网上的解决方法及结合我自己遇到的坑,最后想出的解决办法如下:
着色模式
- 向
DecorView
中添加一个 View, 高度为状态栏的高度(反射获取). - 将
ChildView
的 marginTop 加上状态栏的高度,以此来模拟fitsSystemWindows
. - 设置
ChildView
的 fitsSystemWindow 为 false, 不预留系统栏位置. - 为
DecorView
设置一个 tag, 防止重复添加 View.
这里与其他地方不同的是:
- 向
ContentView
添加 View 在部分机型(华为)上没有效果. - 向
ContentParent
上添加 View 会有一条黑线. - 使用 marginTop 而不是
fitsSystemWindows
是因为无法在不重启 Activity 的情况下切换 root layout 的fitsSystemWindows
属性, 即直接设置不会生效, 所以用 marginTop 来模拟.
全屏模式
- 设置
ChildView
的 fitsSystemWindow 为 false, 不预留系统栏位置. - 如果在
ChildView
的 marginTop 中添加了状态栏的高度, 则移除. - 设置 tag, 防止重复移除.
CollaspingToolbarLayout
这个 support 包中的控件, 由于重写了
这个 support 包中的控件, 由于重写了
onApplySystemInsets()
方法, 按照我所理解的状态栏模式, 它在滑动时在两种模式中切换, 对此我的兼容方法就是让其处于 着色模式 下,在滑动时保持状态栏颜色不变. 当然有更好的解决办法, 但我这里为了方便调用(只需要传递 Activity 对象), 就用了比较简单的处理方法.关于博客和库
博客主要提供思路解析,因为博客的通知不及时,大家有问题有想法想交流时还请在 Github issues 页联系.
评论
发表评论