Android4.4以前的版本,状态栏都是一块黑色的,个人认为还是比较丑的。自4.4开始,Android已经支持透明状态栏了(俗称沉浸式状态栏)。个人认为支持沉浸式状态栏的app逼格还是比较高的,为了紧跟潮流,我们项目中也准备加入沉浸式状态栏。在实现沉浸式状态栏的过程中踩了不少的坑,特此记录下来。
Android 4.4版本提供了FLAG_TRANSLUCENT_STATUS,在Activity中加入此flag,可以设置状态栏透明。代码如下:
1 2 3 4 5 |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { Window window = getWindow(); // Translucent status bar window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); } |
仅仅设置FLAG_TRANSLUCENT_STATUS,你会发现界面上的ToolBar会跑到状态栏上面去,如下图:
通常我们会使用fitsSystemWindows属性来解决此问题。
我们试着给ToolBar设置一下fitsSystemWindows属性为true。布局代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="example.com.chenjinshidemo.MainActivity"> <android.support.v7.widget.Toolbar android:id="@+id/my_toolbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/colorPrimary" android:minHeight="?attr/actionBarSize" android:fitsSystemWindows="true" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" /> <FrameLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:padding="16dp"> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#e0e0e0" android:layout_gravity="bottom"> <EditText android:layout_width="match_parent" android:layout_height="40dp" android:fitsSystemWindows="true" android:background="@drawable/edit_text_rect_bg" /> RelativeLayout> FrameLayout> LinearLayout> |
4.4的效果图如下:
注:有些4.4的系统上面状态栏并不是全透明的,而是渐变的。
你会发现,已经实现了沉浸式状态栏效果了。如果运行在5.0以上的机器上面,会发现大部分手机会出现状态栏是半透明的,效果图如下:
我们能不能让将5.0以上的手机也设置为和4.4一样的全透明的状态栏呢?答案是肯定的!Android自5.0起,又为我们提供了设置状态栏颜色的API,我们可以自己设置状态栏的颜色。
在代码中再加入如下代码:
1 2 3 4 5 6 7 |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); window.setStatusBarColor(Color.TRANSPARENT); } |
再在运行看看效果,状态栏已经变成全透明了。6.0运行效果图和上面4.4一样,就不再附图了。
默认状态栏字体颜色是白色的,如果ToolBar的颜色较浅,那么状态栏上白色的字看不怎么清楚。
Android6.0以后,我们可以使用代码将状态栏字体的颜色设置为黑色了,代码如下:
1
|
win.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
|
设置了深色状态栏字体的效果图如下:
如果你认为已经已经完美实现了,那真是too young to simple。下面是一些我踩过的坑。
如果在界面中有EditText的话,你会发现当软件盘弹出的时候(Activity已经设置了adjustResize),ToolBar的内容都被顶上去了,但是EditText输入框却被有顶上来(正常情况应该是ToolBar没事,输入框被软键盘顶上去),如下图:
这是为什么呢?经研究发现原来是fitsSystemWindows属性搞的鬼。哪个View设置了fitsSystemWindows=true,这个View就会被软件盘顶上去。所以说,fitsSystemWindows不能乱用,会有意想不到的坑。
那能不能不用fitsSystemWindows呢?既然上面说了,fitsSystemWindows=true的作用是给View添加值为状态栏高度的padding,那我们何不自己手动给ToolBar添加padding呢?
我们去掉ToolBar上的fitsSystemWindows属性,并设置一下ToolBar的padding,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
protected void setStatusBarPaddingAndHeight(View toolBar) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (toolBar != null) { int statusBarHeight = getSystemBarHeight(this); toolBar.setPadding(toolBar.getPaddingLeft(), statusBarHeight, toolBar.getPaddingRight(), toolBar.getPaddingBottom()); toolBar.getLayoutParams().height = statusBarHeight + (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 45, getResources().getDisplayMetrics()); } } } |
去掉ToolBar的fitsSystemWindows属性,并加上加上上面的代码,软键盘弹出时ToolBar正常了,但是输入框还是没有弹出来。
刚才上面给ToolBar设置了fitsSystemWindows=true,结果ToolBar的内容被顶上去了,那我们能不能给输入框设置一个fitsSystemWindows=true属性呢?试一下就知道了!
试了之后你会发现,果然可以,但是输入框的高度变了,其实是输入框的padding增加了状态栏的高度。如果设计和产品能接受这种效果,那这也不失为一种解决方法。很显然,一般都不会接受这种效果的,就算设计和产品能接受,我们开发也不能接受!
那有没有更好的方法呢?到网上搜索发现下面一种解决方案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
public class AndroidBug5497Workaround { public static void assistActivity(View content) { new AndroidBug5497Workaround(content); } private View mChildOfContent; private int usableHeightPrevious; private ViewGroup.LayoutParams frameLayoutParams; private AndroidBug5497Workaround(View content) { if (content != null) { mChildOfContent = content; mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { public void onGlobalLayout() { possiblyResizeChildOfContent(); } }); frameLayoutParams = mChildOfContent.getLayoutParams(); } } private void possiblyResizeChildOfContent() { int usableHeightNow = computeUsableHeight(); if (usableHeightNow != usableHeightPrevious) { //如果两次高度不一致 //将计算的可视高度设置成视图的高度 frameLayoutParams.height = usableHeightNow; mChildOfContent.requestLayout();//请求重新布局 usableHeightPrevious = usableHeightNow; } } private int computeUsableHeight() { //计算视图可视高度 Rect r = new Rect(); mChildOfContent.getWindowVisibleDisplayFrame(r); return (r.bottom); } } |
添加上面的类,然后在Activity的onCreate方法中的setContentView后面加上如下代码:
1
|
AndroidBug5497Workaround.assistActivity(findViewById(android.R.id.content));
|
然后运行,输入框能够正常被顶上去,而且输入框的布局有没有受到影响。
该解决方案的原理是,给界面的根布局设置一个监听器,当界面大小有变化的时候,如键盘弹出的时候,重新设置一下根布局的高度,再调用requestLayout对界面进行重绘。
注:不知道这种解决方案会不会引起其他的问题,目前暂时没有发现,如果哪位知道有什么问题,请指点一下,谢谢!
将上面的沉浸式代码放在EMUI3.1系统的手机(如华为荣耀7)上面跑,你会发现,根本没有沉浸式效果,状态栏是透明的,显示的是桌面上的颜色,如下图:
经验证,原来是EMUI3.1系统的原因,很多App(如网易云音乐等)也是在EMUI3.0上有沉浸式的效果,到了EMUI3.1却没有效果了。在EMUI3.1没有沉浸式效果如果和4.4以前一样是黑的也就算了,这样透明的显示桌面颜色实在难看。
后来发现去掉下面这句代码,可以让其有沉浸式的效果。
1
|
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
效果如下:
不过它的状态栏不是全透明的,而是像某些4.4的系统一样是渐变的,不过总比原来的效果好。
这里我们加一个判断,判断如果不是EMUI3.1的系统,才调用clearFlags清除掉FLAG_TRANSLUCENT_STATUS。
具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { Window window = getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // 因为EMUI3.1系统与这种沉浸式方案API有点冲突,会没有沉浸式效果。 // 所以这里加了判断,EMUI3.1系统不清除FLAG_TRANSLUCENT_STATUS if (!isEMUI3_1()) { window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); } window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); window.setStatusBarColor(Color.TRANSPARENT); } } public static boolean isEMUI3_1() { if ("EmotionUI_3.1".equals(getEmuiVersion())) { return true; } return false; } private static String getEmuiVersion(){ Class classType = null; try { classType = Class.forName("android.os.SystemProperties"); Method getMethod = classType.getDeclaredMethod("get", String.class); return (String)getMethod.invoke(classType, "ro.build.version.emui"); } catch (ClassNotFoundException e) { DebugUtil.exception(TAG,e); } catch (NoSuchMethodException e) { DebugUtil.exception(TAG,e); } catch (IllegalAccessException e) { DebugUtil.exception(TAG,e); } catch (InvocationTargetException e) { DebugUtil.exception(TAG,e); } catch (Exception e){ DebugUtil.exception(TAG,e); } return ""; } |
ActionMode是一种Context Menu,它悬浮在ToolBar活着ActionBar上面。现在已经基本上很少app在用ActionMode了,所以可能很多人可能没有用过,没用过的可以看看这篇文章http://blog.csdn.net/xyz_lmn/article/details/12754785。
公司项目中使用到了ActionMode(历史遗留代码),在实现沉浸式的效果中,发现ActionMode并不支持沉浸式。ActionMode弹出来的时候,状态栏会变成黑色的,效果如下:
ActionMode弹出前:
ActionMode弹出后:
遇到这个问题的时候,第一想法就是能不能和ToolBar一样给ActionMode设置一个值为状态栏高度的padding,然后将它顶到状态栏里面去。
在Stackoverflow上面搜了一种方法可以将ActionMode顶到状态栏上面去,给Activity加一个Flag即可,代码如下:
1
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
|
看到能将ActionMode顶到状态栏中去时心里已经在开始偷着乐了,接下来只要给ActionMode设置一个padding即可。然而发现ActionMode根本没有提供在代码中设置高度和padding的API,只能在style中设置高度和padding。这样就有一个问题,因为Android手机碎片化严重,导致不同厂商的不同手机状态栏的高度不一致,所以使用这个方法会出现有的手机ActionMode弹出时比ToolBar高或者低,不过也还能接受。
如果仅仅这样也就算了,没想到又引起了另外一个问题。在使用上面的flag之后(flag不能乱加啊,血和泪的教训),虽然ActionMode顶到状态栏了,但是在某些(如华为)带虚拟按键的手机(虚拟按键对开发者来说也是一个大坑),虚拟按键会遮挡底部的布局。
只能放弃这种方案,尼玛,怎么这么多坑,让我哭会(泪崩)!
没办法,问题还是得去解决啊!继续寻找其它解决方案。。。
这时候想到了在Android5.0以上我们可以设置状态栏的颜色,那可不可以在ActionMode弹出来的时候,给状态栏设置一个与ToolBar颜色一致的颜色呢?尝试一下吧,在BaseActivity中重写startSupportActionMode方法,在里面给状态栏设置颜色,具体代码如下:
1 2 3 4 5 6 7 |
@Override public ActionMode startSupportActionMode(ActionMode.Callback callback) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { getWindow().setStatusBarColor(ContextCompat.getColor(getApplicationContext(), R.color.actionbar_background)); } return super.startSupportActionMode(callback); } |
没想到,居然可以。不过只能兼容5.0以上的手机,4.4还是黑色。目前也只能这样了,后期项目中估计会将ActionMode干掉吧,到时候就OK了。如果大家又更好兼容ActionMode的方法请指点一下,谢谢!!!