前言
Android从4.4开始提出透明状态栏的概念,在5.0之后才有比较像样的沉浸式状态栏,虽然不能修改状态栏图标颜色,至少体验变得更好了,但是对于开发者来说,Android因为版本分裂,导致没法统一,对于开发者来说的确是一件比较头疼的事,下面让我们来看看其中的坑吧。
沉浸式状态栏实现
5.0及以上手机想要做到沉浸式状态栏还是比较容易的(仅仅针对官方Android来说),代码如下:
1 2 3 4 5 6
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Window window = getActivity().getWindow(); window.getDecorView() .setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); window.setStatusBarColor(Color.TRANSPARENT); }
|
此时,你会发现另外一个问题(上图):华为有些机型(EMUI 3.1,Android 5.0.2 ,PLK-AL10)就算是Android 5.0以上,也没法做到状态栏透明,会遮挡部分内容,这样的方案是绝对不会允许的。只能针对华为手机进行适配,判断如果是华为手机,则是用如下方案:
1 2 3 4 5 6 7 8 9 10
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Window window = getActivity().getWindow(); window.getDecorView() .setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); window.setStatusBarColor(Color.TRANSPARENT); if (RomUtils.isEmui()) { window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); } }
|
其中RomUtils,由网友@夏末m提供,其代码如下:
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
| public class RomUtils { private static final String TAG = "RomUtils"; public static final String ROM_MIUI = "MIUI"; public static final String ROM_EMUI = "EMUI"; public static final String ROM_FLYME = "FLYME"; public static final String ROM_OPPO = "OPPO"; public static final String ROM_SMARTISAN = "SMARTISAN"; public static final String ROM_VIVO = "VIVO"; public static final String ROM_QIKU = "QIKU"; private static final String KEY_VERSION_MIUI = "ro.miui.ui.version.name"; private static final String KEY_VERSION_EMUI = "ro.build.version.emui"; private static final String KEY_VERSION_OPPO = "ro.build.version.opporom"; private static final String KEY_VERSION_SMARTISAN = "ro.smartisan.version"; private static final String KEY_VERSION_VIVO = "ro.vivo.os.version"; private static String sName; private static String sVersion; public static boolean isEmui() { return check(ROM_EMUI); } public static boolean isMiui() { return check(ROM_MIUI); } public static boolean isVivo() { return check(ROM_VIVO); } public static boolean isOppo() { return check(ROM_OPPO); } public static boolean isFlyme() { return check(ROM_FLYME); } public static boolean is360() { return check(ROM_QIKU) || check("360"); } public static boolean isSmartisan() { return check(ROM_SMARTISAN); } public static String getName() { if (sName == null) { check(""); } return sName; } public static String getVersion() { if (sVersion == null) { check(""); } return sVersion; } public static boolean check(String rom) { if (sName != null) { return sName.equals(rom); } if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_MIUI))) { sName = ROM_MIUI; } else if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_EMUI))) { sName = ROM_EMUI; } else if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_OPPO))) { sName = ROM_OPPO; } else if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_VIVO))) { sName = ROM_VIVO; } else if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_SMARTISAN))) { sName = ROM_SMARTISAN; } else { sVersion = Build.DISPLAY; if (sVersion.toUpperCase().contains(ROM_FLYME)) { sName = ROM_FLYME; } else { sVersion = Build.UNKNOWN; sName = Build.MANUFACTURER.toUpperCase(); } } return sName.equals(rom); } public static String getProp(String name) { String line = null; BufferedReader input = null; try { Process p = Runtime.getRuntime().exec("getprop " + name); input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024); line = input.readLine(); input.close(); } catch (Exception ex) { NTLog.e(TAG, "Unable to read prop " + name + ex); return null; } finally { if (input != null) { try { input.close(); } catch (Exception e) { e.printStackTrace(); } } } return line; } }
|
解决了状态栏的问题,又发现了另外一个问题(上图):跟帖输入框在软键盘弹起时,被挡住了。没法办,继续寻找解决方案。在网上搜索了一番,发现这个是Android存在已久的bug,有人已经提供了解决方案,如下:
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 (Activity activity) { new AndroidBug5497Workaround(activity); } private View mChildOfContent; private int usableHeightPrevious; private FrameLayout.LayoutParams frameLayoutParams; private AndroidBug5497Workaround(Activity activity) { FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content); mChildOfContent = content.getChildAt(0); mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { public void onGlobalLayout() { possiblyResizeChildOfContent(); } }); frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams(); } private void possiblyResizeChildOfContent() { int usableHeightNow = computeUsableHeight(); if (usableHeightNow != usableHeightPrevious) { int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight(); int heightDifference = usableHeightSansKeyboard - usableHeightNow; if (heightDifference > (usableHeightSansKeyboard/4)) { frameLayoutParams.height = usableHeightSansKeyboard - heightDifference; } else { frameLayoutParams.height = usableHeightSansKeyboard; } mChildOfContent.requestLayout(); usableHeightPrevious = usableHeightNow; } } private int computeUsableHeight() { Rect r = new Rect(); mChildOfContent.getWindowVisibleDisplayFrame(r); return (r.bottom - r.top); } }
|
一开始看到这个方案的时候,纵观代码,没有其他大问题,而且因为根布局的UI和Activity的生命周期是一致的,因此不用mChildOfContent.getViewTreeObserver().removeOnGlobalLayoutListener()也不会发生内存泄露,因此可以先尝试一下。
其原理也十分简单,就是监听整个布局,如果布局发生了变化了超过屏幕1/4高度,则可认为是键盘弹起了,此时,重新设置根布局LayoutParams即可。更详细信息可以参考:Android爬坑之旅:软键盘挡住输入框问题的终极解决方案。
然而,并不管用!你会发现,输入框被虚拟键盘挡住了(上图)!不过,这个问题也不难解决,我们只需要把第一次根布局的高度记录下来,键盘收起的时候,把跟布局高度还原即可。最终代码如下:
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
|
public class AndroidBug5497Workaround { public static void assistActivity(Activity activity) { new AndroidBug5497Workaround(activity); } private View mChildOfContent; private int mUsableHeightPrevious; private FrameLayout.LayoutParams mFrameLayoutParams; private int mContentHeight; private boolean mIsContentHeightMeasured = false; private AndroidBug5497Workaround(Activity activity) { FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content); mChildOfContent = content.getChildAt(0); mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver .OnGlobalLayoutListener() { public void onGlobalLayout() { if ( !mIsContentHeightMeasured ) { mContentHeight = mChildOfContent.getHeight(); mIsContentHeightMeasured = true; } possiblyResizeChildOfContent(); } }); mFrameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams(); }
private void possiblyResizeChildOfContent() { int usableHeightNow = computeUsableHeight(); if (usableHeightNow != mUsableHeightPrevious) { int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight(); int heightDifference = usableHeightSansKeyboard - usableHeightNow; if (heightDifference > (usableHeightSansKeyboard / 4)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { mFrameLayoutParams.height = usableHeightSansKeyboard - heightDifference + SystemUtils.getStatusBarHeight(); } else { mFrameLayoutParams.height = usableHeightSansKeyboard - heightDifference; } } else { mFrameLayoutParams.height = mContentHeight; } mChildOfContent.requestLayout(); mUsableHeightPrevious = usableHeightNow; } }
private int computeUsableHeight() { Rect r = new Rect(); mChildOfContent.getWindowVisibleDisplayFrame(r); return (r.bottom - r.top); } }
|
后来又发现,全屏状态下播放视频内容显示不全(上图),推测是因为添加了全局layout监听,并且重新改了跟布局的显示高度,因此在全屏播放时把监听去掉,非全屏播放时加上监听,再重新运行,终于正常了!最终代码如下:
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
|
public class AndroidBug5497Workaround { private ViewTreeObserver.OnGlobalLayoutListener mOnGlobalLayoutListener = null; private FrameLayout.LayoutParams mFrameLayoutParams; private FrameLayout.LayoutParams mOrigLayoutParams; private boolean mIsContentHeightMeasured = false; private int mUsableHeightPrevious; private View mChildOfContent; private int mContentHeight; public AndroidBug5497Workaround(Activity activity) { if (activity != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content); mChildOfContent = content.getChildAt(0); mFrameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams(); mOrigLayoutParams = new FrameLayout.LayoutParams(mFrameLayoutParams); addGlobalLayoutListener(); } }
public void addGlobalLayoutListener() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { removeGlobalLayoutListener(); mOnGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() { public void onGlobalLayout() { if (!mIsContentHeightMeasured) { mContentHeight = mChildOfContent.getHeight(); mIsContentHeightMeasured = true; } possiblyResizeChildOfContent(); } }; mChildOfContent.getViewTreeObserver() .addOnGlobalLayoutListener(mOnGlobalLayoutListener); } } public void removeGlobalLayoutListener() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (mOnGlobalLayoutListener != null && mChildOfContent != null) { mFrameLayoutParams = new FrameLayout.LayoutParams(mOrigLayoutParams); mChildOfContent.setLayoutParams(mFrameLayoutParams); mChildOfContent.getViewTreeObserver() .removeOnGlobalLayoutListener(mOnGlobalLayoutListener); mOnGlobalLayoutListener = null; } } }
private void possiblyResizeChildOfContent() { int usableHeightNow = computeUsableHeight(); if (usableHeightNow != mUsableHeightPrevious) { int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight(); int heightDifference = usableHeightSansKeyboard - usableHeightNow; if (heightDifference > (usableHeightSansKeyboard / 4)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { mFrameLayoutParams.height = usableHeightSansKeyboard - heightDifference + SystemUtils.getStatusBarHeight(); } else { mFrameLayoutParams.height = usableHeightSansKeyboard - heightDifference; } } else { mFrameLayoutParams.height = mContentHeight; } mChildOfContent.setLayoutParams(mFrameLayoutParams); mChildOfContent.requestLayout(); mUsableHeightPrevious = usableHeightNow; } }
private int computeUsableHeight() { Rect r = new Rect(); mChildOfContent.getWindowVisibleDisplayFrame(r); return (r.bottom - r.top); } }
|
该类是用方法也比较简单,新建一个对象,然后在监听横竖屏变化即可.
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| AndroidBug5497Workaround mAndroidBugFixer; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setCustomActionbar(); } private void setCustomActionbar() { if (getActivity() == null) return; ActionBar.LayoutParams lp = new ActionBar.LayoutParams(ActionBar.LayoutParams.MATCH_PARENT, ActionBar.LayoutParams.MATCH_PARENT); mActionBarView = LayoutInflater.from(getActivity()) .inflate(R.layout.news_video_detail_action_bar_layout, null); mBackView = (ImageView) mActionBarView.findViewById(R.id.action_bar_back); mBackView.setOnClickListener(this); mTitleView = (TextView) mActionBarView.findViewById(R.id.action_bar_title); ActionBar actionBar = getActionBar(); if (actionBar != null) { actionBar.setCustomView(mActionBarView, lp); actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM); actionBar.setDisplayShowCustomEnabled(true); actionBar.setDisplayShowHomeEnabled(false); actionBar.setDisplayShowTitleEnabled(false); actionBar.setBackgroundDrawable( getResources().getDrawable(android.R.color.transparent)); } if (SystemUtils.isLollipop()) { Window window = getActivity().getWindow(); window.getDecorView() .setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); if (RomUtils.isEmui()) { window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); } window.setStatusBarColor(Color.TRANSPARENT); if (getActivity() != null) { mAndroidBugFixer = new AndroidBug5497Workaround(getActivity()); } } } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { if (mAndroidBugFixer != null) { mAndroidBugFixer.removeGlobalLayoutListener(); } } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { if (mAndroidBugFixer != null) { mAndroidBugFixer.addGlobalLayoutListener(); } } }
|
PS:如果发现是用了该类,屏幕有跳一下,闪一下的现象,可以去掉adjustPan标记
1 2
| getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
|
搞定了状态栏和输入框的问题,后来视觉又要求actionbar-title要居中,不过这也不是什么难事,自定义actionbar就可以了。最终代码如下:
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 42 43 44 45
| 自定义actionbar view private void setCustomActionbar() { if (getActivity() == null) return; ActionBar.LayoutParams lp = new ActionBar.LayoutParams(ActionBar.LayoutParams.MATCH_PARENT, ActionBar.LayoutParams.MATCH_PARENT); mActionBarView = LayoutInflater.from(getActivity()) .inflate(R.layout.news_video_detail_action_bar_layout, null); mBackView = mActionBarView.findViewById(R.id.action_bar_back); mBackView.setOnClickListener(this); mTitleView = (TextView) mActionBarView.findViewById(R.id.action_bar_title); ActionBar actionBar = getActionBar(); if (actionBar != null) { actionBar.setCustomView(mActionBarView, lp); actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM); actionBar.setDisplayShowCustomEnabled(true); actionBar.setDisplayShowHomeEnabled(false); actionBar.setDisplayShowTitleEnabled(false); actionBar.setBackgroundDrawable( getResources().getDrawable(android.R.color.transparent)); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Window window = getActivity().getWindow(); window.getDecorView() .setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); window.setStatusBarColor(Color.TRANSPARENT); if (RomUtils.isEmui()) { window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); } window.setStatusBarColor(Color.TRANSPARENT); if (getActivity() != null) { mAndroidBugFixer = new AndroidBug5497Workaround(getActivity()); } } } public ActionBar getActionBar() { if (getActivity() instanceof FragmentActivity) { return ((FragmentActivity)getActivity()).getSupportActionBar(); } return null; }
|
总结
这次解决问题的过程参考了网上的解决方案, 经过了严格的测试,才把代码提交上去。个人对于网上第三方开源库其实是有些谨慎的,原因有二:其一,网上的开源库不一定适合当前项目,可能会有兼容问题(support-v4、support-v7);其二,就算能解决,也不能保证该开源库没有其他问题。这也是这次做状态栏透明没有使用第三方开源库的原因。个人还是比较倾向于自己写,毕竟自己写的代码自己放心。