GitHub源码:TransparentStatusbar
源码中分两个app
认识透明状态栏
从Android4.4
开始引入了透明状态栏的新特性.
见下图,左边为传统的Android
系统状态栏,右边为透明状态栏.
- 正常显示状态栏的图标/文字
- 状态栏的背景是透明的,能透出应用的背景色.而不像之前一样是默认的黑色不可编辑.
透明状态栏Api及特性
从Android 4.4(v19)
开始,透明状态栏特性变化很频繁,直到Android 6.0(v23)
才真正完善稳定.
下表展示各版本所引入的新Api
或特性.
Version/level |
Features |
Description |
4.4/v19 |
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS |
状态栏是渐变色的半透明 |
|
4.4_Watch/v20 |
OnApplyWindowInsetsListener |
能够区分多个Inset事件与Rect信息(PS.系统状态栏属于插入区Inset的一种) |
5.0/v21 |
WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
允许自定义状态栏背景色了,但无法控制状态栏上的文字/图标颜色 |
6.0/v23 |
View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR |
状态栏上的图标/文字颜色的亮色模式,即颜色是暗色 |
设置透明状态栏
根据多个版本间的Api
及特性,Java
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
int visibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
}
window.getDecorView().setSystemUiVisibility(visibility);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.TRANSPARENT);
}
else
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
|
注意: 要设置透明状态栏的Activity
其theme
须是NoTitleBar
.
1
2
3
4
5
6
7
8
9
10
|
<activity android:name=
"MyActivity"
android:theme=
"@android:style/Theme.NoTitleBar"
/>
public
void
onCreate(Bundle bundle) {
super.onCreate(bundle);
requestWindowFeature(Window.FEATURE_NO_TITLE);
}
|
代码执行后,界面显示效果如下图:
可以发现系统状态栏的区域已经消失,Activity
的contentView顶上去占据了原来属于系统状态栏的区域.
导致虽然Back
Title
虽然仍在Titlebar
区域垂直居中,但视觉效果上受状态栏图标的影响,却不是垂直居中的效果.
所以接下来第二步就是: 以何种方式处理消失的系统状态栏区域?
处理消失的系统状态栏区域
处理方式可以有:
- 就让
activity
的contentView
顶上去吧,不需要修改.
- 调整
Titlebar
的高度.
- 可以通过设置
paddingTop
/layout_height
.
- 对于不同的Android版本,可以通过版本适配文件,在
values/dimens.xml
和values-v19/dimens.xml
分别定义具体数值.
- 调整
paddingTop
,有可能导致Titlebar
中的内容不会再垂直居中.
- 不适应无
Titlebar
的activity
.
android:fitsSystemWindows
&& OnApplyWindowInsetsListener
fitsSystemWindows
标签可以直接对View
添加paddingXXX
- 从
activity
的布局嵌套结构中只对第一个设置fitsSystemWidnows
的View
有效,无法设置设置到多个View
- 方式单一,对于指定的界面实现简单,但要应用于整个
App
超多Activity
的layout.xml
,不够灵活.
- 不够灵活还表现在:有些
activity
的复杂效果可能会有多个View
同时或分场合占据系统状态栏的空间,需要留出额外的修改接口.
- 在
activity
的contentView
的顶部再addView
直接填充原来状态栏的区域.
OnApplyWindowInsetsListener
可以回调给开发者当前WindowInset的区域类型与区域宽高Rect信息
- 和
fitsSystemWindows
一样,多个View
设置该监听但也只有最外层的view会被调用执行.
在实践中,本人采用了1
2
5
这三种方式配合使用.
fitsSystemWindows
我并不想用该属性.
这里只记录一下系统源码中的相应的方法:
1
2
3
4
5
6
|
View.java
public WindowInsets
dispatchApplyWindowInsets(WindowInsets insets)
private
boolean
fitSystemWindowsInt(Rect insets)
// 这个方法是真正为View添加paddingXXX的地方
protected
void
internalSetPadding(
int left,
int top,
int right,
int bottom)
|
调试系统源码的一个方法:
使用Android
自带模拟器Debug
,断点跟进执行过程.要注意,模拟器的apk level
要和compileSdkVersion
及buildToolsVersion
相对应.
Activity中的接口设计
接口设计的原则:
- 对正常的业务布局xml的编写没有强制要求
如不要求强制使用fitsSystemWindows
- 不影响正常的业务Activity的java代码编写
如业务Actiivty不需要额外的编码量即可实现透明状态栏效果.特殊的动效Activity除外.
- 提供灵活的处理方式
可方便的开启或关闭透明状态栏功能.
类图如下:
BaseActivity
是App
中所有Activity
的父类.
由于透明状态栏与Activity
相关,所以对应的接口声明都放在BaseActivity
中.
默认Activity
的透明状态栏功能是开启的.
该类中几个重要函数的调用顺序为:
1
2
|
`onCreate
` →
`setContentView
` →
`isFixTransparentStatusbar
`
└──
true→
`fixTransparentStatusbar`
|
具体代码实现为:
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
31
32
33
34
35
36
37
38
39
40
41
42
43
|
@Override
protected
void
onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public
void
setContentView(View view) {
rootView = view;
super.setContentView(view);
if (isFixTransparentStatusBar()) {
Window window = getWindow();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
int visibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
visibility |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
}
window.getDecorView().setSystemUiVisibility(visibility);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.TRANSPARENT);
fixTransparentStatusBar(view);
fixTransparentStatusBarWhiteTextColor(view, viewStatusbarBackground);
}
else
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
fixTransparentStatusBar(view);
fixTransparentStatusBarWhiteTextColor(view, viewStatusbarBackground);
}
else {
setStatusbarBackgroundGone();
}
}
else {
setStatusbarBackgroundGone();
}
}
|
注意 : 由于透明状态栏是执行Window.addFlags()
实现的,该方法又调用了Window.setFlags()
.
阅读该Api
文档,发现推荐先执行setContentView
后执行Window.setFlags()
.
TitlebarActivity
是通用的包含Titlebar
的Activity
.
重载了setContentView()
,实现自动添加Titlebar
这个通用组件,当然不需要Titlebar
时也可以使用setContentViewNoTitlebar()
.
扩展此功能,即在添加通用Titlebar
前先添加上通用的viewStatusbarBackground
.
setContentView()
的实现为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
@Override
public
void
setContentView(View view) {
contentView = view;
LinearLayout linearLayout =
new LinearLayout(
this);
linearLayout.setOrientation(LinearLayout.VERTICAL);
LayoutInflater.
from(
this).inflate(R.layout.transparent_status_bar_bg_view, linearLayout,
true);
viewStatusbarBackground = linearLayout.findViewById(R.id.status_bar_background);
LayoutInflater.
from(
this).inflate(R.layout.titlebar_original, linearLayout,
true);
viewTitlebar = linearLayout.findViewById(R.id.titlebar_layout);
initTitlebarIDs(viewTitlebar);
linearLayout.addView(contentView,
new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
super.setContentView(linearLayout);
}
|
其中,为了便于定位到Titlebar
及viewStatusbarBackground
,这两个组件的id
都被预先定义在attrs.xml
中.
1
2
3
4
5
|
<resources>
<item name=
"status_bar_background"
type=
"id"/>
<item name=
"titlebar_layout"
type=
"id"/>
</resources>
|
Fragment中的接口设计
有的Activity
的显示主体是Fragment
,接口设计的观点为不应干扰Fragment
正常的onCreateView()
的实现流程.
那么在哪个时机处理Fragment
的contentView
呢?
阅读Api
发现了Fragment::onViewCreated(View view)
这个方法,该方法会在onCreateView()
返回后,立即执行,且方法参数为onCreateView()
所返回的View
.
Java代码实现如下:
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
|
public
class BaseFragment extends Fragment {
@Override
public
void
onViewCreated(View view, Bundle savedInstanceState) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
&& isFixTransparentStatusBar()) {
fixTransparentStatusBar(view);
}
super.onViewCreated(view, savedInstanceState);
}
/**
* 是否需要改变status bar背景色,对于某些机型手机(如oppo)无法改变状态栏字体颜色,
* 会被当前状态栏挡住字体颜色,因此修改透明状态栏背景色
*
@return true: 调用fixTransparentStatusBar()
*/
protected
boolean
isFixTransparentStatusBar(){
return
false;
}
/**
*
@param view {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}中返回的view.
* */
protected
void
fixTransparentStatusBar(View view) {
}
}
|
通过fixTransparentStatusBar()
,即可以调整Fragment
的界面显示,无论是往状态栏区域添加一个填充View
或根据id
再调整宽高或padding
都是可以的.
白色Titlebar
的处理
Android 6.0
及以上可以使用亮色模式.
但在是低版本的手机中,Titlebar
如果是白色的,或者说App
的主题是白色的,则会出现状态栏的白色文字和图标被淹没在Titlebar
中无法阅读.如下图:
这时可以通过layer-list
来设置分层背景,不必新增额外的View
填充系统状态栏区域.
见如下代码或TestBasic/res/drawable/title_layout_white3.xml
:
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
|
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@android:color/black" />
</shape>
</item>
<item android:bottom="1dp">
<shape android:shape="rectangle">
<solid
android:color=
"@android:color/white" />
</shape>
</item>
<item android:bottom="48dp">
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape=
"rectangle">
<gradient
android:startColor=
"@android:color/white"
android:centerColor=
"@color/middleColor"
android:endColor=
"@android:color/darker_gray"
android:angle=
"90"
/>
</shape>
</item>
</layer-list>
|
- 第一个
item
为黑色背景,效果为Titlebar
底下的黑色分隔线.
- 第二个
item
为常规的Titlebar
背景.
- 第三个
item
为状态栏的过滤渐变背景色.
最张效果见下图:
React-Native的处理
React-Native
是js
代码,怎么办?
不不,React
是表象,Native
是实质。一样处理掉。
React-Native
最root
的组件界面是ReactRootView
,可以在显示的Activity
里布局使用LinearLayout
,orientation
为VERTICAL
,
将TransparentStatusBar
及ReactRootView
一并添加为子View
,设置该LinearLayout
为Activity
的contentView
即可。
代码如下:
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
|
public
class ElnReactBaseActivity extends BaseActivity {
private ReactInstanceManager mReactInstanceManager;
private ReactRootView mReactRootView;
@Override
protected
void
onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout linearLayout =
new LinearLayout(
this);
linearLayout.setOrientation(LinearLayout.VERTICAL);
viewStatusbarBackground = LayoutInflater.from(
this).inflate(R.layout.transparent_status_bar_bg_view, linearLayout,
false);
linearLayout.addView(viewStatusbarBackground);
mReactRootView =
new ReactRootView(
this);
LinearLayout.LayoutParams layParams =
new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
linearLayout.addView(mReactRootView, layParams);
mReactInstanceManager = ReactHelper.getInstance().getReactManager();
Bundle bundle = getExtra();
mReactRootView.startReactApplication(mReactInstanceManager,
"ELearning", bundle);
setContentView(linearLayout);
}
}
|
小米 与 魅族 与 (莫名其妙的)华为
小米 与 魅族都能通过自各的反射方法实现状态栏的亮色模式,解决白色Titlebar
的问题.
这点两家做得很好.这里直接给出官方文档说明了.
小米状态栏变色
魅族状态栏变色
上述的代码也整合进了GitHub中的工程TitlebarBelowTransparentStatusBar
.
至于华为,额…大部分华为机子都是好机,但华为荣耀6 Plus(PE-TL10,EMUI3.1,Android 5.1.1)明明是Android 5.1,但使用5.1的代码无效,得使用4.4的实现方式.
腾讯优测UTest
一个方便使用的App远程测试平台,机型多,Android版本齐全.
出了华为这档子事,就把App上传试了下其它各种手机,还好还好,没发现其它妖娥子.
About Sodino