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系统对状态栏进行视觉修改,尤其是浅色导航栏的应用。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

时间: 2024-08-02 16:28:52

Android透明化和沉浸式状态栏实践及源码分析的相关文章

详解Android中的沉浸式状态栏效果实例_Android

无意间了解到沉浸式状态栏,感觉贼拉的高大上,于是就是试着去了解一下,就有了这篇文章.下面就来了解一下啥叫沉浸式状态栏.传统的手机状态栏是呈现出黑色条状的,有的和手机主界面有很明显的区别.这一样就在一定程度上牺牲了视觉宽度,界面面积变小. Google从android kitkat(Android 4.4)开始,给我们开发者提供了一套能透明的系统ui样式给状态栏和导航栏,这样的话就不用向以前那样每天面对着黑乎乎的上下两条黑栏了,还可以调成跟Activity一样的样式,形成一个完整的主题,和IOS7

Android RecyclerView的Item自定义动画及DefaultItemAnimator源码分析_Android

这是关于RecyclerView的第二篇,说的是如何自定义Item动画,但是请注意,本文不包含动画的具体实现方法,只是告诉大家如何去自定义动画,如何去参考源代码.  我们知道,RecyclerView默认会使用DefaultItemAnimator,所以如果我们需要自定义动画,那么应该好好的读读这个类的源代码,这样不仅仅是学习怎么自定义,还要学习Android的设计模式.  先弄明白一件事,DefaultItemAnimator继承自SimpleItemAnimator,SimpleItemAn

Android RecyclerView的Item自定义动画及DefaultItemAnimator源码分析

这是关于RecyclerView的第二篇,说的是如何自定义Item动画,但是请注意,本文不包含动画的具体实现方法,只是告诉大家如何去自定义动画,如何去参考源代码. 我们知道,RecyclerView默认会使用DefaultItemAnimator,所以如果我们需要自定义动画,那么应该好好的读读这个类的源代码,这样不仅仅是学习怎么自定义,还要学习Android的设计模式. 先弄明白一件事,DefaultItemAnimator继承自SimpleItemAnimator,SimpleItemAnim

Android 沉浸式状态栏与隐藏导航栏实例详解

1 前言 一般我们在Android的APP开发中,APP的界面如下: 可以看到,有状态栏.ActionBar(ToolBar).导航栏等,一般来说,APP实现沉浸式有三种需求:沉浸式状态栏,隐藏导航栏,APP全屏 沉浸式状态栏是指状态栏与ActionBar颜色相匹配, 隐藏导航栏不用多说,就是将导航栏隐藏,去掉下面的黑条. APP全屏是指将状态栏与导航栏都隐藏,例如很多游戏界面,都是APP全屏. 所以,在做这一步时,关键要问清楚产品狗的需求,免得白费功夫. 下面,分别来介绍这三种方式的实现. 2

关于Android沉浸式状态栏的问题

问题描述 关于Android沉浸式状态栏的问题 通过在每个activity中添加这样代码的方式实现的: 但是在有些手机上,下方的三个基础操作键时这样的: 这时候点击触发的是系统的三个键而不是想要点击的下方的几个按钮了. 为什么会这样,沉浸式除了状态栏之外也会影响这里吗?应该如何解决 解决方案 简要:android从4.4版本及以后加入了沉浸式状态栏的设置,但是也有一些问题,特别是自定义的最上面导航栏时,会出现一些问题,我想就我遇到的问题给大家一些讲解. 设置方法: 第一行代码:透明状态栏 第二行

Android 4.4以上&quot;沉浸式&quot;状态栏效果的实现方法_Android

什么是沉浸式状态栏? 沉浸式状态栏意思指状态栏的颜色随着软件颜色而改变,使状态栏和软件颜色保持一致,沉浸其中!当我们打开应用程序时,不会再因为看到应用程序和状态栏的黑边相隔开而感到十分难看.沉浸式状态栏由于其能给用户群体带来极佳的用户体验,已经在越来越多的应用上得到了体现. 实现原理       从4.4后系统增加了透明状态栏的特性WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS一旦添加上这个属性后,那么布局中的内容DecorView就会自动填

Android沉浸式状态栏 如何改变状态图标和文字的颜色

问题描述 Android沉浸式状态栏 如何改变状态图标和文字的颜色 当我把状态栏设置成白色的时候文字和图标都看不到,而QQ却可以.求助~~~ 解决方案 改变状态栏文字的颜色android 沉浸式之改变小米状态栏颜色 解决方案二: 我是来领积分的啦啦啦啦啦啦啦啦啦 解决方案三: 没看懂什么鬼啊!一串英语搞什么

Android沉浸式状态栏与输入框上移的冲突问题

问题描述 Android沉浸式状态栏与输入框上移的冲突问题 在应用中设置了沉浸式状态栏但是输入框在输入法弹出后不自动上移了不做沉浸式就会上移怎么解决这个冲突做了沉浸式后android:windowSoftInputMode=""stateVisible|adjustResize""这个属性就没效果了 解决方案 http://blog.csdn.net/carlos1992/article/details/46773059 解决方案二: android:windowS

android状态栏一体化、沉浸式状态栏(兼容低版本)

摘要 Android 沉浸式状态栏.状态栏一体化.透明状态栏.仿ios透明状态栏 Google 在 4.4 给全屏阅读文字或玩游戏这种情景增加了透明状态栏和透明导航栏的功能. 首先上两张乐家驿站应用的截图,实际效果大家也可以下载体验,乐家驿站下载:http://www.wandoujia.com/apps/com.haier.cabinet.customer      注意:本文是自定义标题栏,不用android默认自带的标题栏. 第一步:设置 Acitivity 所在 window 的属性