前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android 透明状态栏(伪沉浸式)

Android 透明状态栏(伪沉浸式)

作者头像
三流编程
发布2018-09-11 16:17:19
2.3K0
发布2018-09-11 16:17:19
举报

4.4 以上要做所谓沉浸式,其实不是真正意义上的沉浸式,只是一种透明状态栏。

而由于 Android API 的不同,需要考虑 4.4、5.0、6.0 前后的不同。

适配 5.0 和 6.0 以上

应用风格如果是白色的,想把状态栏也设置成白色的,会导致状态栏上的图标文字看不见了,经查询发现 6.0 以上可以修改状态栏图标文字风格,可以改成黑的,但是 6.0 以下版本无解。体验了 QQ 浏览器,因为网页大多都是纯白的,在 6.0 的手机上状态栏背景纯白,图标文字改成黑的了,但在 5.1 的手机上图标文字没法改,它是把背景做成灰色的了。

6.0 以下无法改状态栏图标文字颜色,只能控制颜色不要太白。

代码语言:javascript
复制
window = this.activity.getWindow();
decorView = window.getDecorView();
// 设置状态栏颜色
window.setStatusBarColor(statusBarColorBefore23);

6.0 以上可以根据状态栏要变化的颜色来调整状态栏图标文字的风格。

代码语言:javascript
复制
// isLightStatusBarAfter23 控制是否更改状态栏图标文字颜色
int flag = isLightStatusBarAfter23 ? View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR : View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
decorView.setSystemUiVisibility(flag);
window.setStatusBarColor(statusBarColorAfter23); // 设置状态栏颜色

适配 4.4

4.4 版本需要透明状态栏,将内容往下移,然后再加一个和状态栏一样大小的 View 覆盖到状态栏上面。

代码语言:javascript
复制
rootView = ((ViewGroup)decorView.findViewById(android.R.id.content)).getChildAt(0);

window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
rootView.setFitsSystemWindows(true);

View view = new View(activity);
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity));
view.setBackgroundColor(statusBarColorBefore23);
view.setLayoutParams(params);
// 过去有遇到过在某版 MIUI 上这么加状态栏下面会有黑边
//             ((ViewGroup)decorView.findViewById(android.R.id.content)).addView(view);
((ViewGroup)decorView).addView(view);

自动获取布局背景色

如果没指定颜色,自动获取根 View 的背景,还找不到的话,再找第一个子 View,一开始递归找第一个 View 的,感觉没什么意义,调用者一般应该明确传颜色,不传可能就是根 View 上设了背景之类。这就要考虑设的是颜色还是图片。第一个子 View 是图片还是普通 View 设了背景。因为如果是图片,就不能设置状态栏颜色或者盖个 View 上去,而是让状态栏透明,内容往下,让图片透上去,当然如果是子 View 的图片,还不能 setFitsSystemWindows。

代码语言:javascript
复制
private boolean setStatusBarWithViewBg(View view, boolean isRootView) {
    Drawable drawable = view.getBackground();
    if (drawable != null) { // 设置了背景
        if (drawable instanceof ColorDrawable) {
            statusBarColorBefore23 = statusBarColorAfter23 = ((ColorDrawable) drawable).getColor();
            // ... 根据颜色去设置
        } else { // 背景是一张图
            window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            view.setFitsSystemWindows(isRootView); // 如果第一个子View的图片,要顶上去,不要下来,只有根 View 才下来

            // 如果是子 View,因为图片要上去,图片里的内容得下来,所以加个 Padding
            view.setPadding(view.getPaddingLeft(),
                    view.getPaddingTop() +
                    (isRootView ? 0 :
                            getStatusBarHeight(activity),
                    view.getPaddingRight(), view.getPaddingBottom());
        }
        return true;
    } else {
        return false;
    }
}

4.4 版本和 setFitsSystemWindows 各种奇怪问题

setFitsSystemWindows 设置一次后再设置就没用了,有时明明是 true 内容又跑上去了,明明是 false 确跑下来了,反正多次调用这方法就各种问题。还遇到过 setFitsSystemWindows 导致内容布局变化,如果不对每个 Activity 配置一次 android:configChanges="screenSize|screenLayout",引起 onCreate 的多次调用。

所以尽量用 setPadding 来调整位置。

代码语言:javascript
复制
if (paddingTop == -1) {
    paddingTop = rootView.getPaddingTop();
}
view.setPadding(view.getPaddingLeft(),
                paddingTop + getStatusBarHeight(mActivity),
                view.getPaddingRight(),
                view.getPaddingBottom());

因此 4.4 版本也要修改

代码语言:javascript
复制
private static final String TAG_KITKAT = "kitkat";

window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);

View view = decorView.findViewWithTag(TAG_KITKAT);
if (view != null) {
    view.setBackgroundColor(statusBarColorBefore23);
} else if (rootView instanceof ViewGroup) {
    view = new View(mActivity);
    ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight);
    view.setBackgroundColor(statusBarColorBefore23);
    view.setLayoutParams(params);
    view.setTag(TAG_KITKAT); // 加个Tag,下次直接获取该 View 更改颜色
    ((ViewGroup)decorView).addView(view);
}

/*
if (paddingTop == -1) {
    paddingTop = rootView.getPaddingTop();
}
*/
rootView.setPadding(view.getPaddingLeft(),
        paddingTop + statusBarHeight,
        view.getPaddingRight(), view.getPaddingBottom());

项目中遇到一个问题,基类设置了一个默认的状态栏样式,但某些 Activity 要自己单独的样式,又创建了一个对象,结果专门做沉浸的这个类被构造了两遍,导致 paddingTop 计算错误。搞了两遍,第二次 paddingTop 变成了两个状态栏高度加原来自己的 paddingTop,花了好长时间才排查出来。

所以解决方案就是基类构造的对象作为属性保存下来,然后子类就用父类的属性。

状态的重置

因为考虑同一个 Activity 多次改变状态栏颜色的情况,遇到的一个比较烦的问题是,许多状态需要重置,不然就会影响下一次,而且如果设置图片又改成颜色的,那么要考虑的更多,一会希望图片内容顶到状态栏下面,一会希望内容能在状态栏下面。

后来考虑将颜色和图片的逻辑分开,因为有图片时要重置的和只是改状态栏颜色的不一样,放一起如果只是改状态栏颜色会走大量无意义的逻辑,当然 4.4 版本也是要将内容往下,也要特殊考虑。

代码语言:javascript
复制
private void reset(int newMode) {
  if (lastMode == MODE_IMAGE) {
      if (firstChildPaddingTop >= 0 && firstChildView != null) {
          setPaddingTop(firstChildView, firstChildPaddingTop);
      }
      if (rootPaddingTop >= 0) {
          setPaddingTop(rootView, rootPaddingTop);
      }
      if (newMode == MODE_COLOR) {
          window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
      }
  } else if ((lastMode == MODE_COLOR) && (newMode == MODE_IMAGE)) {
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // 5.0 以上
          window.setStatusBarColor(Color.TRANSPARENT);
      } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // 4.4
          View view = decorView.findViewWithTag(TAG_KITKAT);
          if (view != null) {
              ((ViewGroup) decorView).removeView(view); // 把前面加的 View 移除
          }
          if (rootPaddingTop >=0) {
              setPaddingTop(rootView, rootPaddingTop);
          }
      }
  }

  lastMode = newMode;
}

还有最后每次设置完效果后要充值颜色值,以免影响下次使用

代码语言:javascript
复制
statusBarColorBefore23 = statusBarColorAfter23 = 0;
isLightStatusBarAfter23 = true;

支持第三方 SDK 页面

如果是第三方的 SDK,跳转的 Activity 是 SDK 里面的,可以用 ActivityLifecycleCallbacks,在 ActivityLifecycleCallbacks 里可以拿到 Activity 的实例,这里可以做沉浸。

代码语言:javascript
复制
public void onActivityStarted(Activity activity) {
    ...
    if (activity.getClass().getName().startsWith("第三方SDK包名前缀")) {
        new PseudoImmersiveModeManager(activity)
            .setStatusBarColor(Color.GRAY, Color.WHITE)
            .setIsLightStatusBarAfter23(true)
            .makeStatusBarImmersive();
    }
    ...
}

之所以不在 onActivityCreated 里调用,是因为虽然 Activity 实例是有了,但是页面还没加载完成,获取 rootView 时报空指针。

支持 DialogFragment

在 onCreateDialog 或 onViewCreated 的回调里,反正就是 Dialog 创建好了后调用

代码语言:javascript
复制
getDialog().getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
View view = getDialog().getWindow().getDecorView();
view.setPadding(view.getPaddingLeft, statusBarHeight, view.getPaddingRight, view.getPaddingBottom);

详细代码请见 Github 地址 ,下面分别是在 5.0 和 6.0 手机上的效果:

immersive5.gif

immersive6.gif

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.03.28 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 适配 5.0 和 6.0 以上
  • 适配 4.4
  • 自动获取布局背景色
  • 4.4 版本和 setFitsSystemWindows 各种奇怪问题
  • 状态的重置
  • 支持第三方 SDK 页面
  • 支持 DialogFragment
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档