【Android】透明状态栏在App中的实现与接口设计
文章目录
GitHub 源码:
源码中分两个
- 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
代码如下:
- 12345678910111213141516171819
| - // 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); }
|
- 12345678910
| - // AndroidManifest.xml "MyActivity" android:theme="@android:style/Theme.NoTitleBar" /> // Or / 或 // MyActivity.java public void onCreate(Bundle bundle) { super.onCreate(bundle); requestWindowFeature(Window.FEATURE_NO_TITLE); }
|
代码执行后, 界面显示效果如下图:
可以发现系统状态栏的区域已经消失,
- Activity
的 contentView 顶上去占据了原来属于系统状态栏的区域. 导致虽然
- Back
- Title
虽然仍在
- Titlebar
区域垂直居中, 但视觉效果上受状态栏图标的影响, 却不是垂直居中的效果.
所以接下来第二步就是: 以何种方式处理消失的系统状态栏区域
处理消失的系统状态栏区域
处理方式可以有:
在实践中, 本人采用了
- 1
- 2
- 5
这三种方式配合使用.
fitsSystemWindows
我并不想用该属性.
这里只记录一下系统源码中的相应的方法:
- 123456
| - 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
的透明状态栏功能是开启的.
该类中几个重要函数的调用顺序为:
- 12
| - `onCreate` → `setContentView` → `isFixTransparentStatusbar` └──true→ `fixTransparentStatusbar`
|
具体代码实现为:
- 12345678910111213141516171819202122232425262728293031323334353637383940414243
| - // 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(); } }
|
- 12345678910111213141516171819
| - @Overridepublic 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
中.
- 12345
| - // values/attrs.xml<resources> "status_bar_background" type="id"/> "titlebar_layout" type="id"/></resources>
|
Fragment 中的接口设计
有的
- Activity
的显示主体是
- Fragment
, 接口设计的观点为不应干扰
- Fragment
正常的
- onCreateView()
的实现流程. 那么在哪个时机处理
- Fragment
的
- contentView
呢? 阅读
- Api
发现了
- Fragment::onViewCreated(View view)
这个方法, 该方法会在
- onCreateView()
返回后, 立即执行, 且方法参数为
- onCreateView()
所返回的
- View
.
Java 代码实现如下:
- 12345678910111213141516171819202122232425262728
| - 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 #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
:
- 123456789101112131415161718192021222324252627
| - <?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
为状态栏的过滤渐变背景色.
最张效果见下图:
小米 与 魅族 与 (莫名其妙的) 华为
小米 与 魅族都能通过自各的反射方法实现状态栏的亮色模式, 解决白色
- Titlebar
的问题. 这点两家做得很好. 这里直接给出官方文档说明了.
上述的代码也整合进了 GitHub 中的工程
- TitlebarBelowTransparentStatusBar
.
至于华为, 额… 大部分华为机子都是好机, 但华为荣耀 6 Plus(PE-TL10,EMUI3.1,Android 5.1.1) 明明是 Android 5.1, 但使用 5.1 的代码无效, 得使用 4.4 的实现方式.
腾讯优测 UTest
一个方便使用的 App 远程测试平台, 机型多, Android 版本齐全.
出了华为这档子事, 就把 App 上传试了下其它各种手机, 还好还好, 没发现其它妖娥子.
来源: