Android透明化和沉浸式状态栏实践及源码分析
本文所提到的透明状态栏其实指的是将顶部的导航栏延伸到状态栏,使之浑然一体(google官方建议状态栏颜色比导航栏的颜色略深一点),并不代表一定不设置背景色,比如导航栏是白色,则可设置状态栏为白色,视情况而定。
相比于ios系统,android系统对于状态栏的设置就显得稍微复杂了一点。android系统提供了api 19以上对状态栏的设置接口,而直到api 23以上才提供对于icon颜色的设置,还有就是各家厂商(如魅族,小米等)对于状态栏的有自己的定制,对于需要使用浅色背景状态栏的应用,没处理好的话往往导致浅色背景,白色icon ,状态栏不分你我的悲剧。。
(内心os:嗯?右上角那一个绿色的电池,用户一定知道他是状态栏对吧。)
我随即对比了一些主流app,发现在我的魅蓝2(android 5.1 flyme 4.5)上竟然都不支持透明状态栏,这对于我这种追求审美的人(其实是视觉提的需求)来说简直不能忍。在我折腾了几天之后,终于解决了这些问题,希望对大家思路有一些帮助。
言归正传,本文主要针对以下几点进行分析:1.是否隐藏状态栏(全屏模式) 2.状态栏的背景色的设置 3.状态栏icon的颜色的设置,而对于透明状态栏设置过程中,可能造成的icon颜色设置成功,而背景颜色设置失败,等等原因造成的浅色底,浅色字或深色底,深色字等错误情况的处理及兜底方案 4.源码实现。
下面我们就以上几点来讨论一下android中透明状态栏的实现。
1. 全屏模式(沉浸式状态栏)
这种情况其实用得并不多,基本上使用场景在闪屏页展示广告或logo,以及一些阅读类app需要尽可能的利用到屏幕大小,展示更多的内容。
设置方法很简单,分两种,在api > 16时:
theme中定义:将自定义该activtiy的theme,并在其中添加
<item name="android:windowfullscreen">true</item>
代码中定义:
window.getdecorview().setsystemuivisibility(view.system_ui_flag_layout_fullscreen)
2. 状态栏的背景色
我们都知道,在android5.0(即api > 21)时,google官方提供接口设置对应的状态栏背景色
window.setstatusbarcolor(@colorint int color);
那我们要想在android5.0以下设置背景色就真的没有办法了吗?并不是,我们发现在android4.4以后,出现了windowtranslucentstatus这一特性,所以思路如下:
- 先设置状态栏的透明属性
- 给根布局顶部加上一个和状态栏一样大小的矩形view,充当假的状态栏
- 设置fitssystemwindows属性为true,此时跟布局会延伸到状态栏,处在顶部位置就是之前设置的view,这样就可以以假乱真了。
if (build.version.sdk_int >= build.version_codes.kitkat) { // 设置状态栏透明 activity.getwindow().addflags(windowmanager.layoutparams.flag_translucent_status); // 生成一个状态栏大小的矩形view view statusview = createstatusview(activity, color); viewgroup decorview = (viewgroup) activity.getwindow().getdecorview(); decorview.addview(statusview); // 设置根布局的参数 viewgroup rootview = (viewgroup) ((viewgroup) activity.findviewbyid(android.r.id.content)).getchildat(0); rootview.setfitssystemwindows(true); rootview.setcliptopadding(true); }
设置矩形色块的代码如下:
private static view createstatusview(activity activity, int color) { // 获得状态栏高度 int resourceid = activity.getresources().getidentifier("status_bar_height", "dimen", "android"); int statusbarheight = activity.getresources().getdimensionpixelsize(resourceid); // 绘制一个和状态栏一样高的矩形 view statusview = new view(activity); linearlayout.layoutparams params = new linearlayout.layoutparams(viewgroup.layoutparams.match_parent, statusbarheight); statusview.setlayoutparams(params); statusview.setbackgroundcolor(color); return statusview; }
效果如下:
3. 状态栏icon的颜色
我们通过调研发现,魅族和小米的两个厂商分别对于flyme及miui做了状态栏的定制,所以我们可以在flyme4.0以上及miuiv6以上都可以实现icon颜色的替换,结合上述的状态栏背景色的替换,我们就可以对于魅族及小米两款机型适配浅色的导航栏了。
3.1 魅族flyme4.0+
/** * 魅族flyme4+以上透明状态栏 * @param window * @param dark 是否把状态栏字体及图标颜色设置为深色 * @return 是否设置成功 */ public static boolean setmeizustatusbardarkicon(window window, boolean dark) { boolean result = false; if (window != null) { try { windowmanager.layoutparams lp = window.getattributes(); field darkflag = windowmanager.layoutparams.class .getdeclaredfield("meizu_flag_dark_status_bar_icon"); field meizuflags = windowmanager.layoutparams.class .getdeclaredfield("meizuflags"); darkflag.setaccessible(true); meizuflags.setaccessible(true); int bit = darkflag.getint(null); int value = meizuflags.getint(lp); if (dark) { value |= bit; } else { value &= ~bit; } meizuflags.setint(lp, value); window.setattributes(lp); result = true; } catch (exception e) { //错误处理 } } return result; }
3.2 miuiv6.0+
/** * 设置状态栏字体图标为深色,需要miuiv6以上 * @param window * @param dark 是否把状态栏字体及图标颜色设置为深色 * @return 是否设置成功 * */ public static boolean setmiuistatusbarlightmode(window window, boolean dark) { boolean result = false; if (window != null) { class clazz = window.getclass(); try { int darkmodeflag = 0; class layoutparams = class.forname("android.view.miuiwindowmanager$layoutparams"); field field = layoutparams.getfield("extra_flag_status_bar_dark_mode"); darkmodeflag = field.getint(layoutparams); method extraflagfield = clazz.getmethod("setextraflags", int.class, int.class); if(dark){ extraflagfield.invoke(window,darkmodeflag,darkmodeflag);//状态栏透明且黑色字体 }else{ extraflagfield.invoke(window, 0, darkmodeflag);//清除黑色字体 } result=true; }catch (exception e){ //错误处理 } } return result; }
效果如下:
4. 设置背景色失败处理方案
4.1 解决思路
说了那么多,有童鞋可能就问了,上述说的代码可能对大多数机型有效,但是万一某些机型设置失败了呢?体验不但没提升,反倒让用户不能忍受了。
好吧,下面我们来看一下应该怎么解决。思路如下:
- 获取状态栏的背景色,与需要设置的颜色进行比对,如果相等则设置成功。
- 如果背景色设置失败,针对非flyme机型,我们回退状态栏icon颜色,还原到最初状态。
- 上述为什么要强调非flyme机型,是因为用过flyme机型的童鞋肯定知道有一个透明状态栏的开关,当开启后,flyme会对状态栏进行处理,但处理效果并不和我们想要的一样(比如视图顶部为黑色背景,但是却设置了白色状态栏),并且两者都对状态栏进行操作,操作的冲突也有可能引起上述的icon颜色不明显的情况,所以我们自己定义一个colordrawable进行替换。
4.2 获取状态栏颜色
/** * 获取状态栏颜色 * * @param window 需要获取的activity * @return color 状态栏的颜色值 */ public static int getcolor(window window){ if (build.version.sdk_int >= build.version_codes.lollipop) { return window.getstatusbarcolor(); }else if (build.version.sdk_int >= build.version_codes.kitkat) { viewgroup decorview = (viewgroup) window.getdecorview(); int count = decorview.getchildcount(); if (count > 0 && decorview.getchildat(count - 1) instanceof statusbarview) { int color = color.transparent; drawable background = decorview.getchildat(count - 1).getbackground(); if (background instanceof colordrawable) color = ((colordrawable) background).getcolor(); return color; } } return -1; }
下面讲一下针对如何屏蔽flyme机型沉浸式状态栏打开时的影响:
private static boolean setmeizustatuscolor(window window, @colorint int color, int alpha) { if (build.version.sdk_int >= build.version_codes.lollipop) { final view view = window.getdecorview().findviewbyid(android.r.id.statusbarbackground); if (view != null) { if (!(view.getbackground() instanceof meizucolordrawable)) { //替换成自定义colordrawable view.setbackground(new meizucolordrawable()); } if (view.getbackground() instanceof meizucolordrawable) { meizucolordrawable colordrawable = (meizucolordrawable) view.getbackground(); int finalcolor = statusbarutil.calculatestatuscolor(color, alpha); view.setbackgroundcolor(finalcolor); colordrawable.setcolor(finalcolor); return true; } } } return false; }
4.3 兜底方案
上面提到了一些失败判断途径,那么如果还是对一些机型无效呢?我的建议是可以在本地预埋一个开关,万一出问题,服务器可以对相应机型的沉浸式开关关掉即可,或者采用hotfix等方式来进行修复。
5. 源码分析
看过上面针对魅族和小米设置icon的源代码我们知道,共同点都是通过反射来进行调用相应的方法,或者对于相应的状态栏视图进行设置颜色。我们来看看源码,在window中有一个抽象方法是setstatusbarcolor而在phonewindow中我们找到了该方法的实现
@override public void setstatusbarcolor(int color) { mstatusbarcolor = color; mforcedstatusbarcolor = true; if (mdecor != null) { mdecor.updatecolorviews(null, false /* animate */); } }
mdecor.updatecolorviews()
private windowinsets updatecolorviews(windowinsets insets, boolean animate) { ... updatecolorviewint(mnavigationcolorviewstate, sysuivisibility, mnavigationbarcolor, navbarsize, navbartorightedge, 0 /* rightinset */, animate && !disallowanimate); boolean statusbarneedsrightinset = navbartorightedge && mnavigationcolorviewstate.present; int statusbarrightinset = statusbarneedsrightinset ? mlastrightinset : 0; updatecolorviewint(mstatuscolorviewstate, sysuivisibility, mstatusbarcolor, mlasttopinset, false /* matchvertical */, statusbarrightinset, animate && !disallowanimate); ... }
那么mnavigationcolorviewstate和mstatuscolorviewstate又是什么呢?别着急,我们继续往下看
private final colorviewstate mstatuscolorviewstate = new colorviewstate( system_ui_flag_fullscreen, flag_translucent_status, gravity.top, gravity.left, status_bar_background_transition_name, com.android.internal.r.id.statusbarbackground, flag_fullscreen); private final colorviewstate mnavigationcolorviewstate = new colorviewstate( system_ui_flag_hide_navigation, flag_translucent_navigation, gravity.bottom, gravity.right, navigation_bar_background_transition_name, com.android.internal.r.id.navigationbarbackground, 0 /* hidewindowflag */);
mnavigationcolorviewstate和mstatuscolorviewstate这两个都是colorviewstate定义好的colorviewstate,主要存储了关于statusbar的一些视图信息。包括id,translucentflag,verticalgravity等等。
接着看updatecolorviewint()方法,这个方法是对一个view设置颜色的,自然这个view也就是指的状态栏的view,下面来看一下实现:
private void updatecolorviewint(final colorviewstate state, int sysuivis, int color, int size, boolean verticalbar, int rightmargin, boolean animate) { state.present = size > 0 && (sysuivis & state.systemuihideflag) == 0 && (getattributes().flags & state.hidewindowflag) == 0 && (getattributes().flags & flag_draws_system_bar_backgrounds) != 0; boolean show = state.present && (color & color.black) != 0 && (getattributes().flags & state.translucentflag) == 0; boolean visibilitychanged = false; view view = state.view; int resolvedheight = verticalbar ? layoutparams.match_parent : size; int resolvedwidth = verticalbar ? size : layoutparams.match_parent; int resolvedgravity = verticalbar ? state.horizontalgravity : state.verticalgravity; //对传入的colorviewstate的view做非空判断 if (view == null) { if (show) { //新建一个view state.view = view = new view(mcontext); //设置背景色 view.setbackgroundcolor(color); view.settransitionname(state.transitionname); view.setid(state.id); visibilitychanged = true; //有人可能会感觉奇怪,为什么show为true的时候,这里反倒要设置invisible。从后面代码可以知道,这里无非是想通过动画来做一个渐显操作,而不是立马显示出来。 view.setvisibility(invisible); state.targetvisibility = visible; layoutparams lp = new layoutparams(resolvedwidth, resolvedheight, resolvedgravity); lp.rightmargin = rightmargin; addview(view, lp); updatecolorviewtranslations(); } } else { int vis = show ? visible : invisible; visibilitychanged = state.targetvisibility != vis; state.targetvisibility = vis; if (show) { layoutparams lp = (layoutparams) view.getlayoutparams(); if (lp.height != resolvedheight || lp.width != resolvedwidth || lp.gravity != resolvedgravity || lp.rightmargin != rightmargin) { lp.height = resolvedheight; lp.width = resolvedwidth; lp.gravity = resolvedgravity; lp.rightmargin = rightmargin; view.setlayoutparams(lp); } //设置背景色 view.setbackgroundcolor(color); } } //改变状态栏visibility属性时加入动画 if (visibilitychanged) { view.animate().cancel(); if (animate) { if (show) { //显示时先设置为全透明 if (view.getvisibility() != visible) { view.setvisibility(visible); view.setalpha(0.0f); } //关于透明度的动画插值器 view.animate().alpha(1.0f).setinterpolator(mshowinterpolator). setduration(mbarenterexitduration); } else { view.animate().alpha(0.0f).setinterpolator(mhideinterpolator) .setduration(mbarenterexitduration) .withendaction(new runnable() { @override public void run() { state.view.setalpha(1.0f); state.view.setvisibility(invisible); } }); } } else { view.setalpha(1.0f); view.setvisibility(show ? visible : invisible); } } }
6. 总结
话说了这么多,其实就是想让大家知道为什么很多app放弃在较低版本的android系统对状态栏进行视觉修改,尤其是浅色导航栏的应用。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。