关于安卓沉浸式状态栏的研究
2017-01-11 10:21
#旧文章
前言
沉浸式状态栏是一个好东西,但是使用起来并没有想象中的那样一帆风顺。
需要解决的问题
- 我希望状态栏是半透明的,而不仅是比主颜色深
- 在设置了一些 flag 以后,布局跑到了状态栏 / 导航栏底下
设置状态栏和导航栏透明
要使状态栏和导航栏透明,可以直接使用如下代码
public static void setBarsTranslucent(Window window, int color) { if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION | WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); window.setStatusBarColor(color); window.setNavigationBarColor(color); }else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) { window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION | WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); }}
以上代码做了:
- 如果系统版本在 LOLLIPOP 以上,则设置状态栏和导航栏的颜色为 color
其中 color 的格式为 ARGB,推荐使用 0×20000000 - 如果系统版本在 LOLLIPOP 以下,KITKA_WATCH 以上,则设置导航栏和状态栏为透明
其中的各种 flag 的作用不在这里介绍,详细请参照 Android Developer
解决布局进入状态栏 / 导航栏下方的问题
网上的文章提到了 fitsSystemWindows
这一个属性,但是使用起来我感觉不是特别的顺手,所以我翻看了安卓的源码。
其中 View.setFitsSystemWindows(boolean fitSystemWindows)
方法的源码如下
public void setFitsSystemWindows(boolean fitSystemWindows) { setFlags(fitSystemWindows ? FITS_SYSTEM_WINDOWS : 0, FITS_SYSTEM_WINDOWS);}
于是我顺着 FITS_SYSTEM_WINDOWS
这个 flag 向下搜索,找到了如下方法
private boolean fitSystemWindowsInt(Rect insets) { if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) { mUserPaddingStart = UNDEFINED_PADDING; mUserPaddingEnd = UNDEFINED_PADDING; Rect localInsets = sThreadLocal.get(); if (localInsets == null) { localInsets = new Rect(); sThreadLocal.set(localInsets); } boolean res = computeFitSystemWindows(insets, localInsets); mUserPaddingLeftInitial = localInsets.left; mUserPaddingRightInitial = localInsets.right; internalSetPadding(localInsets.left, localInsets.top, localInsets.right, localInsets.bottom); return res; } return false;}
接着我查找了该方法的调用,找到如下方法
protected boolean fitSystemWindows(Rect insets) { if ((mPrivateFlags3 & PFLAG3_APPLYING_INSETS) == 0) { if (insets == null) { return false; } try { mPrivateFlags3 |= PFLAG3_FITTING_SYSTEM_WINDOWS; return dispatchApplyWindowInsets(new WindowInsets(insets)).isConsumed(); } finally { mPrivateFlags3 &= ~PFLAG3_FITTING_SYSTEM_WINDOWS; } } else { return fitSystemWindowsInt(insets); }}
于是接着查找调用
public WindowInsets onApplyWindowInsets(WindowInsets insets) { if ((mPrivateFlags3 & PFLAG3_FITTING_SYSTEM_WINDOWS) == 0) { if (fitSystemWindows(insets.getSystemWindowInsets())) { return insets.consumeSystemWindowInsets(); } } else { if (fitSystemWindowsInt(insets.getSystemWindowInsets())) { return insets.consumeSystemWindowInsets(); } } return insets;}
注意到这一句了吗
insets.getSystemWindowInsets()
似乎在 WindowInsets
上可以做一点文章,所以我翻看了 WindowInsets
的源码
因为 WindowInsets
里面绝大部分方法都不能被访问,而且找不到对应的 getter,所以我查找了 WindowInsets 的使用,于是我发现了下面的接口
public interface OnApplyWindowInsetsListener { public WindowInsets onApplyWindowInsets(View v, WindowInsets insets);}
到这里已经能发现解决方案了。
解决方案
首先我们在 onCreate(Bundle savedInstanceState)
中获取根布局,即 setContentView(View view)
中的 view
rootView = findViewById(R.id.root_view);
接着我们对 rootView 设置 OnApplyWindowInsetsListener
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) { rootView.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() { @Override public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { // System.out.println(insets); if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) { // 在这里获取各个方向的inset // 顶部:insets.getSystemWindowInsetTop(); // 底部:insets.getSystemWindowInsetBotton(); // 左侧:insets.getSystemWindowInsetLeft(); // 右侧:insets.getSystemWindowInsetRight(); } return insets; } });}
写在最后
在这个研究完成后,我惊喜的发现如果此时 Activity 设置有
android:windowSoftInputMode="adjustResize|stateHidden"
那么键盘弹出时布局会自动被压缩,一石二鸟。