关于屏幕适配 ---- 状态栏
关于状态栏 这么多年了 适配主要也就是沉浸式 所以这篇文章主要是说一说沉浸式,还有一些相关的方法
沉浸式的实现方式 从4.4到今天,也有过很多次变更,这里我们只讲一些变动比较大的部分, 我们也一起都看一下
4.4 版本
沉浸式的发展史,最早开始于4.4版本,在4.4之前,隐藏状态栏的方式无外乎设置fullscreen ,但是效果并不灵活,展示的时候与app内容格格不入,不展示的时候又觉得像是猴子身上长虱子哪都不得劲
于是在4.4版本开放了api,可以将状态栏(还有底部虚拟按键)设置为半透明状态
//半透明状态栏 在setContentView前调用
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
并在对应布局xml的最外层增加属性
android:fitsSystemWindows="true"
身边实在没有这么低版本的设备,就直接说了
存在问题还是很明显
1:切换颜色并不灵活,并不能全部透明,对渐变色背景的适配很差
2:根布局设置了fitsSystemWindows后,对子view进行了偏移,避开了状态栏位置,并有安全margin
5.0版本
来到5.0的时候,沉浸式的发展开始欣欣向荣,因为在这个版本提供了一个接口
window.setStatusBarColor可以直接修改状态栏颜色,so
// 首先添加flag让状态栏变为可编辑模式,或者叫可绘制都一样,然后给透明色
Window window = activity.getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.TRANSPARENT);
// 同时也要清除掉半透明的配置
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
上面两步设置可以将状态栏设置为透明状态,但还没解决布局侵入的问题
另外在根布局设置fitsSystemWindows属性的方法不够全面,有部分布局是不会响应这个属性的,比如frameLayout,相信也有人遇到设置无效的情况
所以我们可以去看一下源码,能够影响此属性的布局是如何处理的,以CoordinatorLayout为例
在源码中我们找到了关于fitsSystemWindows在设置为true后,具体做了什么,很明显生效的就是框起来的两行代码,那么同样我们也可以自己去进行设置,以达到这个属性的预期目的
window.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
添加上这一句,就完成了背景侵入状态栏,但还没结束,虽然侵入了,但由于是我们自己对window进行了属性设置,子布局的偏移还没有得到处理,大概是这种情况
所以我们还要自己做一下处理,去获取状态栏的高度,给最顶部的控件一个空间,放一个view也好,给最顶部的加topmargin也好,只要拿到了状态栏的高度,那才是真的好
在这里我准备两种获取状态栏的方式
1.比较稳定的方式
/**
* 推荐
* 获取状态栏高度 (稳定)底层是反射获取
* @param context
* @return
*/
public static int getStatusBarHeight(Context context) {
int height = 0;
int identifier = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (identifier > 0) {
height = context.getResources().getDimensionPixelSize(identifier);
}
return height;
}
2. 9.0提供的方式
/**
* 不推荐
* 获取状态栏高度
* 此方法有一定局限性 此方法不会等待decoredview执行
* 放在onCreate 或 onResume生命周期中 可能为 0
* 特别说明 getSafeInsetTop方法本意为 获取顶部安全距离 即存在水滴刘海时的高度 固仅在设备刘海位置为顶部时结果需要
* 可在绑定window后于 attachToWindow方法调用
*/
@RequiresApi(api = Build.VERSION_CODES.P)
public static int getNotchHeight(Window window) {
int notchHeight = 0;
WindowInsets windowInsets = window.getDecorView().getRootWindowInsets();
if (windowInsets == null) {
return 0;
}
DisplayCutout displayCutout = windowInsets.getDisplayCutout();
if (displayCutout == null || displayCutout.getBoundingRects() == null) {
return 0;
}
notchHeight = displayCutout.getSafeInsetTop();
return notchHeight;
}
基于大部分需求对于沉浸式的要求,在我们开发过程中肯定更偏向于尽可能早的去获取到状态栏的高度,
第一种方式是通过反射获取状态栏的高度,不会被绘制影响
第二种方式是在绘制结束后去获取状态栏实际绘制的属性,因而回收到window绘制进度的影响, 过早的取值会空,在attachToWindow中调用,或者handler.delay去获取
PS:有人可能在心里抬杠,还可以直接用反射,class method invoke巴拉巴拉,第一种方式内部运行的也是反射,所以没有拿上来
在拿到了状态栏高度之后,后面的就简单了,设置上就好,在这里我放个示例代码和效果图,具体代码或者设置方式还是需要各位按照自己面对的需求来制定和封装
// get_notch_height为最顶部view
int notchHeight = NotchSupportUtils.getNotchHeight(getWindow());
Log.e("Screen-Notch", "notchHeight == " + notchHeight);
if (hasNotchForMI) {
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) get_notch_height.getLayoutParams();
params.topMargin = notchHeight;
get_notch_height.setLayoutParams(params);
}
6.0版本
此版本多了一个相关属性
View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
这个属性应该叫 浅色状态栏模式
意味着在背景颜色较浅时,可以切换状态栏内文字的颜色为黑色
这个就不放示例图了,上面那张图就是设置之后的
9.0版本
在这里又是一个不小的变动,在这个版本的特性中,增加了对刘海屏的支持,这使得状态栏被占用的更多了一部分,当然,继续使用之前的适配策略依旧能完成沉浸式,但有些特定需要在状态栏背景做文章的需求就得适配一下了
我就不在这里说 刘海屏 水滴屏 挖空屏分别是什么了,这个随便搜搜就有,还是围绕着适配去说
因为这三种屏幕的出现,官方提供了几个新的window的Attributes的layoutInDisplayCutoutMode属性
LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS:窗口始终占用刘海区域
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT:默认状态,竖屏模式下会流入到刘海区域,横屏会留有黑边
LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES:不论横竖屏均会流入到刘海区域
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER:窗口始终不会占用刘海区域
在这里我就不单独进行演示了,根据自己的需求来设置即可
在设置LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES时需要设置fullscreen,简单放一个9.0的适配代码
WindowManager.LayoutParams attributes = activity.getWindow().getAttributes();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
activity.getWindow().setAttributes(attributes);
View decorView = activity.getWindow().getDecorView();
int systemUiVisibility = decorView.getSystemUiVisibility();
int flags = View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
systemUiVisibility |= flags;
activity.getWindow().getDecorView().setSystemUiVisibility(systemUiVisibility);
}
此外,如果要在刘海屏两侧的状态栏中做文章,在9.0也给出了方法
/**
* 获取刘海具体位置
* 9.0更新方法可以精确获取刘海宽高及位置
* 在fullscreen时可以对状态栏其他位置进行精确布局
* 此方法在decoredview绘制结束时才有值返回,在onCreate或onResume中直接调用可能为0
* 可采用延时调用 如handle.delay 100-200
* 可在绑定window后于 attachToWindow方法调用
* @param window
* @return int[] 0123对应上左下右
*/
@RequiresApi(api = Build.VERSION_CODES.P)
public static int[] getNotchLoaction(Window window) {
int[] loc = {0, 0, 0, 0};
View decorView = window.getDecorView();
WindowInsets rootWindowInsets = decorView.getRootWindowInsets();
if (rootWindowInsets != null) {
DisplayCutout displayCutout = rootWindowInsets.getDisplayCutout();
List<Rect> boundingRects = displayCutout.getBoundingRects();
if (boundingRects != null && boundingRects.size() != 0) {
for (Rect rect : boundingRects) {
loc[0] = rect.top;
loc[1] = rect.left;
loc[2] = rect.bottom;
loc[3] = rect.right;
Log.e("NotchLocation - top", loc[0] + "");
Log.e("NotchLocation - left", loc[1] + "");
Log.e("NotchLocation - bottom", loc[2] + "");
Log.e("NotchLocation - right", loc[3] + "");
}
}
}
return loc;
}
拿到了坐标,剩下的就是做需求就好了
Ps: 9.0也不要忘了处理布局偏移量
小结
沉浸式的发展,记住这几个关键的版本就差不多了,当然,这只是我们初步用于开发,在面试中能快速应答的部分,而不是说沉浸式的知识只有这些
从4.4至今大大小小很多个版本都有对状态栏包括沉浸式有影响的api,只不过在这里只是展示了几个常用的部分
关于状态栏我也是今天学习后记下来,写一遍也是给自己记一遍 ,不对的地方也请指正
另外需要更进阶的对沉浸式对状态栏深入的了解,需要WMS的知识做支撑,在后面我会找时间把wms相关的做一下,这个知识板块比较大,那就看我哪天需求少了得