Sodino的专栏

还是自己的窝舒服,跳转至 http://sodino.com 吧。

【Android】透明状态栏在App中的实现与接口设计

文章目录
  1. 1. 认识透明状态栏
  2. 2. 透明状态栏Api及特性
  3. 3. 设置透明状态栏
  4. 4. 处理消失的系统状态栏区域
  5. 5. fitsSystemWindows
  6. 6. Activity中的接口设计
  7. 7. Fragment中的接口设计
  8. 8. 白色Titlebar的处理
  9. 9. React-Native的处理
  10. 10. 小米 与 魅族 与 (莫名其妙的)华为
  11. 11. 腾讯优测UTest

GitHub源码:TransparentStatusbar
源码中分两个app

  • TestBasic:

    1. 透明状态栏实现的示例,方便debug
    2. 白色/红色Titlebar的不同处理方式
    3. paddingTopfitsSystemWindows的对比
    4. layer-list分层背景的使用
  • TitlebarBelowTransparentStatusBar

    1. 示例App中统一的处理方式
    2. Activity中的接口设计
    3. Fragment中的接口设计

认识透明状态栏

Android4.4开始引入了透明状态栏的新特性.
见下图,左边为传统的Android系统状态栏,右边为透明状态栏.   
 
what is transparent status bar

  • 正常显示状态栏的图标/文字  
  • 状态栏的背景是透明的,能透出应用的背景色.而不像之前一样是默认的黑色不可编辑.

透明状态栏Api及特性

Android 4.4(v19)开始,透明状态栏特性变化很频繁,直到Android 6.0(v23)才真正完善稳定.  
 
下表展示各版本所引入的新Api或特性.

Version/levelFeaturesDescription
4.4/v19WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS状态栏是渐变色的半透明 
4.4_Watch/v20OnApplyWindowInsetsListener能够区分多个Inset事件与Rect信息(PS.系统状态栏属于插入区Inset的一种)
5.0/v21WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
允许自定义状态栏背景色了,但无法控制状态栏上的文字/图标颜色
6.0/v23View.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
// Activity.java
// onCreate(Bundle bundle)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// Android 5.0
int visibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Android 6.0
// 亮色模式,避免系统状态栏的图标不可见
// 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);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// Android 4.4
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}

注意: 要设置透明状态栏的Activitytheme须是NoTitleBar.

1
2
3
4
5
6
7
8
9
10
// AndroidManifest.xml
<activity android:name="MyActivity"
android:theme="@android:style/Theme.NoTitleBar"
/>
// Or / 或
// MyActivity.java
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
requestWindowFeature(Window.FEATURE_NO_TITLE);
}

代码执行后,界面显示效果如下图:
status_bar_space
可以发现系统状态栏的区域已经消失,ActivitycontentView顶上去占据了原来属于系统状态栏的区域.
导致虽然Back Title虽然仍在Titlebar区域垂直居中,但视觉效果上受状态栏图标的影响,却不是垂直居中的效果.

所以接下来第二步就是: 以何种方式处理消失的系统状态栏区域?


处理消失的系统状态栏区域

处理方式可以有:

  1. 就让activitycontentView顶上去吧,不需要修改.
    • 这种场景用于一些可全屏浏览图片/观看视频等界面.
  2. 调整Titlebar的高度.
    • 可以通过设置paddingTop/layout_height.
    • 对于不同的Android版本,可以通过版本适配文件,在values/dimens.xmlvalues-v19/dimens.xml分别定义具体数值.
    • 调整paddingTop,有可能导致Titlebar中的内容不会再垂直居中.
    • 不适应无Titlebaractivity.
  3. android:fitsSystemWindows && OnApplyWindowInsetsListener
    • fitsSystemWindows标签可以直接对View添加paddingXXX
    • activity的布局嵌套结构中只对第一个设置fitsSystemWidnowsView有效,无法设置设置到多个View
    • 方式单一,对于指定的界面实现简单,但要应用于整个App超多Activitylayout.xml,不够灵活.
    • 不够灵活还表现在:有些activity的复杂效果可能会有多个View同时或分场合占据系统状态栏的空间,需要留出额外的修改接口.
  4. activitycontentView的顶部再addView直接填充原来状态栏的区域.
  5. 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要和compileSdkVersionbuildToolsVersion相对应.


Activity中的接口设计

接口设计的原则:

  1. 对正常的业务布局xml的编写没有强制要求
    如不要求强制使用fitsSystemWindows
  2. 不影响正常的业务Activity的java代码编写
    如业务Actiivty不需要额外的编码量即可实现透明状态栏效果.特殊的动效Activity除外.
  3. 提供灵活的处理方式
    可方便的开启或关闭透明状态栏功能.

类图如下:
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
// WhateverActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
// BaseActivity.java
@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);
// 最后fix一下状态栏背景白色与系统的文字图标白色的问题
fixTransparentStatusBarWhiteTextColor(view, viewStatusbarBackground);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// WindowManager.LayoutParams localLayoutParams = window.getAttributes();
// localLayoutParams.flags = (WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS | localLayoutParams.flags);
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
fixTransparentStatusBar(view);
// 最后fix一下状态栏背景白色与系统的文字图标白色的问题
fixTransparentStatusBarWhiteTextColor(view, viewStatusbarBackground);
} else {
setStatusbarBackgroundGone();
}
} else {
setStatusbarBackgroundGone();
}
}

注意 : 由于透明状态栏是执行Window.addFlags()实现的,该方法又调用了Window.setFlags().
阅读该Api文档,发现推荐先执行setContentView后执行Window.setFlags().
Window.setFlags

  • TitlebarActivity 是通用的包含TitlebarActivity.
    重载了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);
}

其中,为了便于定位到TitlebarviewStatusbarBackground,这两个组件的id都被预先定义在attrs.xml中.

1
2
3
4
5
// values/attrs.xml
<resources>
<item name="status_bar_background" type="id"/>
<item name="titlebar_layout" type="id"/>
</resources>

Fragment中的接口设计

有的Activity的显示主体是Fragment,接口设计的观点为不应干扰Fragment正常的onCreateView()的实现流程.
那么在哪个时机处理FragmentcontentView呢?
阅读Api发现了Fragment::onViewCreated(View view)这个方法,该方法会在onCreateView()返回后,立即执行,且方法参数为onCreateView()所返回的View.

Fragment

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中无法阅读.如下图:
white.text.and.icon.invisible
这时可以通过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>
<!-- 48dp为标题栏高度 -->
<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为状态栏的过滤渐变背景色.

最张效果见下图:
white.Titlebar.perfect


React-Native的处理

React-Nativejs代码,怎么办?
不不,React是表象,Native是实质。一样处理掉。
React-Nativeroot的组件界面是ReactRootView,可以在显示的Activity里布局使用LinearLayoutorientationVERTICAL,
TransparentStatusBarReactRootView一并添加为子View,设置该LinearLayoutActivitycontentView即可。

代码如下:

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上传试了下其它各种手机,还好还好,没发现其它妖娥子.

uTest


About Sodino

阅读更多
个人分类: Android
想对作者说点什么? 我来说一句

各种API性能_性能优化技巧

2018年05月08日 0B 下载

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭