咱们先一块儿来回顾一下实现沉浸式状态栏的通常套路。在Android上,关于对StatusBar(状态栏)的操做,一直都在不断改善,而且表现愈来愈好,在Android4.4 如下,咱们能够对StatusBar和 NavigationBar进行显示和隐藏操做。可是直到Android4.4,咱们才能真正意义上的实现沉浸式状态栏。从Android4.4 到如今(Android 9),关于沉浸式大概能够分红三个阶段:android
总结:这三个阶段的Android上API版本混乱,各类Flag林立。再加上各大厂商的定制化可谓是火上浇油,让安卓开发者异常头疼。git
咱们将从沉浸式支持的三个阶段和支持的功能出发,去了解出现的相关背景,而后去了解怎么实现三个阶段的沉浸式。github
在android4.4及以上版本中为 setSystemUiVisibility() 方法引入了一个新的flag:SYSTEMUIFLAGIMMERSIVE,它可使你的app实现真正意义上的全屏体验。当SYSTEMUIFLAGIMMERSIVE、SYSTEMUIFLAGHIDENAVIGATION 和SYSTEMUIFLAG_FULLSCREEN三个flag一块儿使用的时候,能够隐藏状态栏与导航栏,同时让你的app能够捕捉到用户的全部触摸屏事件。**从Android4.4以上版本才是真正的能够设置沉浸式体验,但也仅仅是操做状态栏和导航栏的显示与隐藏**。api
当沉浸式全屏模式启用的时候,你的activity会继续接受各种的触摸事件。用户能够经过在状态栏与导航栏原来区域的边缘向内滑动让系统栏从新显示。这个操做清空了SYSTEMUIFLAGHIDENAVIGATION和SYSTEMUIFLAG_FULLSCREEN,若是没有两个标志的话,系统栏从新变得可见。若是设置了两个标签的话,这个操做同时也触发了View.OnSystemUiVisibilityChangeListener。然而, 若是你想让系统栏在一段时间后自动隐藏的话,你应该使用SYSTEMUIFLAGIMMERSIVESTICKY标签。app
展现了各类不一样的“沉浸式”状态ide
在上图中:布局
注意,immersive类的标签只有在与SYSTEMUIFLAGHIDENAVIGATION,SYSTEMUIFLAG_FULLSCREEN中一个或两个一块儿使用的时候才会生效。你能够只使用其中的一个,可是通常状况下你须要同时隐藏状态栏和导航栏以达到沉浸的效果。测试
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);复制代码
<style name="Theme" parent="Theme.Design.Light.NoActionBar">
<item name="android:windowTranslucentStatus">true</item>
</style>复制代码
效果以下:字体
效果如上图,能够看出,沉浸式的效果是出来了,可是也有一个问题,咱们的标题栏和状态栏重叠了,至关于整个布局上移了StatusBar 的高度。ui
为了让标题栏回到原来的位置而且适应标题栏的颜色,咱们在标题栏的上方添加一个大小和StatusBar大小同样假的状态栏View,View 的BackgroundColor 能够本身设置成标题栏同样的颜色也能够是其余颜色,这个View起到一个占位的做用。这个时候,标题栏就会下移StatusBar的高度,回到正常的位置。
经过设置paddingTop从新绘制标题栏高度代码以下:
View statusBarView = mDecorView.findViewById(IMMERSION_STATUS_BAR_VIEW);
if (statusBarView == null) {
statusBarView = new View(mActivity);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
mBarConfig.getStatusBarHeight());
params.gravity = Gravity.TOP;
statusBarView.setLayoutParams(params);
statusBarView.setVisibility(View.VISIBLE);
statusBarView.setId(IMMERSION_STATUS_BAR_VIEW);
mDecorView.addView(statusBarView);
}
if (mBarParams.statusBarColorEnabled) {
statusBarView.setBackgroundColor(ColorUtils.blendARGB(mBarParams.statusBarColor,
mBarParams.statusBarColorTransform, mBarParams.statusBarAlpha));
} else {
statusBarView.setBackgroundColor(ColorUtils.blendARGB(mBarParams.statusBarColor,
Color.TRANSPARENT, mBarParams.statusBarAlpha));
}复制代码
添加上述代码后,效果以下:
经过以上就能够实现Android 4.4 上的沉浸式状态栏。
小结:Android4.4-Android5.0的步骤就是为window添加FLAGTRANSLUCENTSTATUS的Flag,而后添加一个假的状态栏,经过上述方法设置的沉浸式在Android4.4-Android5.0之间的效果如贴图,状态栏顶部是有一个黑色阴影渐变,在5.0版本版本以上被修复了。
若是是一张图片沉浸到状态栏则不须要设置这个假的状态栏,只须要设置,FLAGTRANSLUCENTSTATUS就OK。而且在Android4.4-Android5.0是没有提供改变状态颜色的属性,因此只能经过新增长一个假的状态栏方式改变背景颜色。
Android 5.0 是一个里程碑式的版本,从Android 5.0开始,Google 推出了全新的设计规范 Material Design,而且原生控件就能够实现一些炫酷的UI动效。从这个版本开始,google 加入了一个比较重要的方法setStatusBarColor (对应属性:android:statusBarColor),经过这个方法,能够很轻松地实现沉浸式状态栏。方法以下
public abstract void setStatusBarColor(@ColorInt int color);复制代码
注意看这个方法的注释,想要这个方法生效,必须还要配合一个Flag一块儿使用,必须设置FLAGDRAWSSYSTEMBARBACKGROUNDS ,而且不能设置FLAGTRANSLUCENTSTATUS(Android 4.4才用这个),因此和4.4互斥不能共用。因此Android5.0以上能够设置状态栏和导航栏字体颜色。
解释:设置了FLAGDRAWSSYSTEMBARBACKGROUNDS,代表会Window负责系统bar的background 绘制,绘制透明背景的系统bar(状态栏和导航栏),而后用getStatusBarColor()和getNavigationBarColor()的颜色填充相应的区域。这就是Android 5.0 以上实现沉浸式导航栏的原理。
实现沉浸式添加以下代码:
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
//注意要清除 FLAG_TRANSLUCENT_STATUS flag
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
getWindow().setStatusBarColor(getResources().getColor(android.R.color.holo_red_light));复制代码
效果以下:
若是在开发的时候是经过设置主题的方式设置,则须要在values-v21文件夹下添加以下主题,达到兼容目的
<style name="Theme" parent="Theme.Design.Light.NoActionBar">
<item name="android:windowTranslucentStatus">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">@android:color/holo_red_light</item>
</style>复制代码
在Android 5.0 使图片延伸到状态栏,只需设置windowTranslucentStatus,将 statusBarColor 设置为透明便可。主题方式设置以下:
<style name="ImageTranslucentTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="android:windowTranslucentNavigation">true</item>
<item name="android:windowTranslucentStatus">true</item>
<!-- 设置statusBarColor 为透明-->
<item name="android:statusBarColor">@android:color/transparent</item>
</style>复制代码
在开发过程当中,使用代码设置windowTranslucentStatus须要经过版本号的判断兼容 Android5.0如下和Android 5.0以上。
使用Android6.0如下版本沉浸式的时候会遇到一个问题,那就是Android 系统状态栏的字色和图标颜色为白色,当状态栏颜色接近浅色的时候,状态栏上的内容就看不清了。Android 6.0 新添加了一个属性来解决这个问题,属性是SYSTEMUIFLAGLIGHTSTATUS_BAR。
解释:为setSystemUiVisibility(int)方法添加的Flag,请求status bar 绘制模式,它能够兼容亮色背景的status bar 。要在设置了FLAGDRAWSSYSTEMBARBACKGROUNDSflag ,同时清除了FLAGTRANSLUCENTSTATUSflag 才会生效。
经过代码设置:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN|View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}复制代码
效果以下而且还能够在主题中使用属性,而且该主题须要放在values-v23文件夹下相应Android6.0以上才能生效:
<style name="Theme" parent="Theme.Design.Light.NoActionBar">
<item name="android:windowTranslucentStatus">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">@android:color/holo_red_light</item>
<!-- Android 6.0以上 状态栏字色和图标为浅黑色-->
<item name="android:windowLightStatusBar">true</item>
</style>复制代码
在实际开发过程当中,咱们不只仅只会遇到以上三种版本兼容问题,还须要考虑如:不一样手机品牌,动态该状态栏背景,以及Fragment中须要有本身的状态栏颜色场景。因此须要综合考虑多种场景,达到能适配多种开发状况的要求。总结出如下场景:
综合以上场景而且参考github例子进行封装以后获得了ZanImmersionBar这个轮子
咱们但愿将设置沉浸式效果都封装在一个类里面,想达到全部的效果经过一个方法设置,但不少的效果中都会有重复的设置步骤,而且每一个效果的方法太多则分不清使用哪一个,因此将设置沉浸式效果拆分红几个步骤,而想要设置个性效果,经过方法设置参数,最后经过init方法收集全部参数让后统一设置参数属性。
ZanImmersionBar.with(this).init();//该方法将进行如下步骤处理沉浸式
public void init() {
//更新Bar的参数
updateBarParams();
//设置沉浸式
setBar();
//适配状态栏与布局重叠问题
fitsLayoutOverlap();
//适配软键盘与底部输入框冲突问题
fitsKeyboard();
//变色view
transformView();
}复制代码
沉浸式设置流程以下图:
这几个步骤其中,获取参数和设置沉浸式是必须通过,下面三种设置是在开发中可能遇到的状况,也是设置参数,若是匹配到了则会进行三种设置的处理,接下来主要分析下第一个步骤和第二步骤。
咱们使用一个对象用于存储用户设置的bar参数,这些参数有状态栏和导航栏颜色、透明度、显示隐藏等等,经过该对象中的参数来分别设置
public class BarParams implements Cloneable {
/**
* 状态栏颜色
*/
@ColorInt
int statusBarColor = Color.TRANSPARENT;
/**
* 导航栏颜色
*/
@ColorInt
int navigationBarColor = Color.BLACK;
/**
* The Default navigation bar color.
*/
int defaultNavigationBarColor = Color.BLACK;
/**
* 状态栏透明度
*/
@FloatRange(from = 0f, to = 1f)
float statusBarAlpha = 0.0f;
/**
* 导航栏透明度
*/
@FloatRange(from = 0f, to = 1f)
float navigationBarAlpha = 0.0f;
//等其余属性
...
}复制代码
而这些属性能够经过如下ZanImmersionBar提供的方法进行个性化设置,而这些方法只是将须要设置的参数添加到BarParams对象中,最后必须调用init将参数设置上去。
ZanImmersionBar.with(this)
.transparentStatusBar() //透明状态栏,不写默认透明色
.transparentNavigationBar() //透明导航栏,不写默认黑色(设置此方法,fullScreen()方法自动为true)
.transparentBar() //透明状态栏和导航栏,不写默认状态栏为透明色,导航栏为黑色(设置此方法,fullScreen()方法自动为true)
.statusBarColor(R.color.colorPrimary) //状态栏颜色,不写默认透明色
.navigationBarColor(R.color.colorPrimary) //导航栏颜色,不写默认黑色
.barColor(R.color.colorPrimary) //同时自定义状态栏和导航栏颜色,不写默认状态栏为透明色,导航栏为黑色
.statusBarAlpha(0.3f) //状态栏透明度,不写默认0.0f
.navigationBarAlpha(0.4f) //导航栏透明度,不写默认0.0F
.barAlpha(0.3f) //状态栏和导航栏透明度,不写默认0.0f
.statusBarDarkFont(true) //状态栏字体是深色,不写默认为亮色
.navigationBarDarkIcon(true) //导航栏图标是深色,不写默认为亮色
//等一些其余方法
.init(); //必须调用方可沉浸式复制代码
设置完参数后,则须要收集这些参数,若是在Fragment中使用,则须要Activity同步Fragment的BarParams参数
//得到Bar相关信息
//若是在Fragment中使用,让Activity同步Fragment的BarParams参数
if (mIsFragment) {
ZanImmersionBar immersionBar = mImmersionBarMap.get(mActivity.toString());
if (immersionBar != null) {
immersionBar.mBarParams = mBarParams;
}
}复制代码
mImmersionBarMap是个Map,用于存储每一个Activity对应的ZanImmersionBar对象
ZanImmersionBar.with(this).destroy(); //必须调用该方法,防止内存泄漏
public void destroy() {
//取消监听
cancelListener();
//删除当前界面对应的ImmersionBar对象
Iterator<Map.Entry<String, ZanImmersionBar>> iterator = mImmersionBarMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, ZanImmersionBar> entry = iterator.next();
if (entry.getKey().contains(mImmersionBarName) || (entry.getKey().equals(mImmersionBarName))) {
iterator.remove();
}
}
}复制代码
该方法则是将上一步的参数进行初始化,初始化过程则会根据沉浸式三个阶段和不一样厂商进行区分设置。须要注意小米手机系统有本身定制化设置的属性,因此须要分区设置
private void setBar() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && !OSUtils.isEMUI3_x()) {
//适配刘海屏
fitsNotchScreen();
//初始化5.0以上,包含5.0
uiFlags = initBarAboveLOLLIPOP(uiFlags);
//android 6.0以上设置状态栏字体为暗色
uiFlags = setStatusBarDarkFont(uiFlags);
//android 8.0以上设置导航栏图标为暗色
uiFlags = setNavigationIconDark(uiFlags);
} else {
//初始化5.0如下,4.4以上沉浸式
initBarBelowLOLLIPOP();
}
...
}
if (OSUtils.isMIUI6Later()) {
//修改miui状态栏字体颜色
setMIUIBarDark(mWindow, MIUI_STATUS_BAR_DARK, mBarParams.statusBarDarkFont);
//修改miui导航栏图标为黑色
if (mBarParams.navigationBarEnable) {
setMIUIBarDark(mWindow, MIUI_NAVIGATION_BAR_DARK, mBarParams.navigationBarDarkIcon);
}
}
...
}复制代码
咱们能够重点看下5.0以上状态栏和导航栏初始化,设置初始化window属性让后设置导航栏和状态栏颜色
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private int initBarAboveLOLLIPOP(int uiFlags) {
//Activity全屏显示,但状态栏不会被隐藏覆盖,状态栏依然可见,Activity顶端布局部分会被状态栏遮住。
uiFlags |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
mWindow.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
//判断是否存在导航栏
if (mBarConfig.hasNavigationBar()) {
mWindow.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
}
//须要设置这个才能设置状态栏和导航栏颜色
mWindow.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
//设置状态栏颜色
if (mBarParams.statusBarColorEnabled) {
mWindow.setStatusBarColor(ColorUtils.blendARGB(mBarParams.statusBarColor,
mBarParams.statusBarColorTransform, mBarParams.statusBarAlpha));
} else {
mWindow.setStatusBarColor(ColorUtils.blendARGB(mBarParams.statusBarColor,
Color.TRANSPARENT, mBarParams.statusBarAlpha));
}
...
return uiFlags;
}复制代码
经过代码分析能够看到其实ZanImmersionBar所作的事情就是将设置沉浸式方法进行步骤拆分和增长个性属性设置,而且将沉浸式三个阶段和不一样厂商进行区分独立方法设置调用。
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 全部子类都将继承这些相同的属性,请在设置界面以后设置
ZanImmersionBar.with(this).init();
}
@Override
protected void onDestroy() {
super.onDestroy();
// 必须调用该方法,防止内存泄漏
ZanImmersionBar.with(this).destroy();
}
}复制代码
注意在Fragment中使用ZanImmersionBar须要在承载的Activity中初始化ZanImmersionBar,否则会抛出异常
public abstract class BaseFragment{
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initImmersionBar();
}
@Override
public void initImmersionBar() {
ZanImmersionBar.with(this).keyboardEnable(true).init();
}
}
复制代码
具体的Fragment调用如下方法:
@Override
public void initImmersionBar() {
super.initImmersionBar();
ZanImmersionBar.with(this)
.statusBarDarkFont(true)
.statusBarColor(R.color.btn1)
.navigationBarColor(R.color.btn1)
.init();
}复制代码
在Dialog中设置ZanImmersionBar方式和在Fragment或者Activity同样,若是在Fragment或者Activity中有设置而且dialog出现不须要改变状态栏则不用设置ZanImmersionBar,若是须要作定制化上面的高级用法在Dialog也支持
ZanImmersionBar.with(this).init();复制代码
ZanImmersionBar.with(this, dialog).init();复制代码
重点是调用如下方法,可是此方法会致使有导航栏的手机底部布局会被导航栏覆盖,还有底部输入框没法根据软键盘弹出而弹出。这个属性在顶部弹出的时候是须要使用,若是是底部弹框须要看状况而定。
popupWindow.setClippingEnabled(false);复制代码
正常使用ZanImmersionBar通常不须要考虑重叠问题但在项目中接入ZanImmersionBar而且页面没有考虑给头部控件预留出状态栏的高度,而且须要将页面内容沉浸到状态栏或者作定制化状态栏,这种状况下须要考虑重叠问题。以前说到Android4.4版本的时候解决重叠的方式是一种,也能够参考一下几种方式解决状态栏与布局顶部重叠问题。
在values-v19/dimens.xml文件下
<dimen name="status_bar_height">25dp</dimen>复制代码
在values/dimens.xml文件下
<dimen name="status_bar_height">0dp</dimen>复制代码
而后在布局界面添加view标签,高度指定为statusbarheight
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/darker_gray"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="@dimen/status_bar_height"
android:background="@color/colorPrimary" />
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
app:title="方法一"
app:titleTextColor="@android:color/white" />
</LinearLayout>复制代码
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:fitsSystemWindows="true">
</LinearLayout>复制代码
而后使用ImmersionBar时候必须指定状态栏颜色
ZanImmersionBar.with(this)
.statusBarColor(R.color.colorPrimary)
.init();复制代码
注意:ZanImmersionBar必定要在设置完布局之后使用
ZanImmersionBar.with(this)
.fitsSystemWindows(true) //使用该属性,必须指定状态栏颜色
.statusBarColor(R.color.colorPrimary)
.init();复制代码
在标题栏的上方增长View标签,高度指定为0dp
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/darker_gray"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="@color/colorPrimary" />
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
app:title="方法四"
app:titleTextColor="@android:color/white" />
</LinearLayout>复制代码
而后使用ZanImmersionBar的statusBarView方法,指定view就能够啦
ZanImmersionBar.with(this)
.statusBarView(view)
.init();
//或者
//ZanImmersionBar.setStatusBarView(this,view);复制代码
ZanImmersionBar.with(this)
.titleBar(view) //能够为任意view,若是是自定义xml实现标题栏的话,最外层节点不能为RelativeLayout
.init();
//或者
//ZanImmersionBar.setTitleBar(this, view);复制代码
ZanImmersionBar.with(this)
.titleBarMarginTop(view) //能够为任意view
.statusBarColor(R.color.colorPrimary) //指定状态栏颜色,根据状况是否设置
.init();
//或者使用静态方法设置
//ZanImmersionBar.setTitleBarMarginTop(this,view);复制代码
在处理Android沉浸式状态栏和导航栏开始会很头大,而且会不理解相关设置的window的FLAG属性,想要分清楚这些属性的大意须要从出现背景出发拆封,先熟悉沉浸式出现的3个阶段的属性和版本能作什么和不能作什么,而后再去了解各个厂家定制化的属性在哪些版本阶段使用,以及是否须要对异形屏适配,最后才是在实际开发和需求中对状态栏和导航栏处理。不过在Android6.0之后版本厂家的定制化状态已经愈来愈没有意义了,之后Android原生趋势也将会让开发者使用沉浸式愈来愈舒服。