专栏首页三流程序员的挣扎Android 透明状态栏(伪沉浸式)

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

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 以下无法改状态栏图标文字颜色,只能控制颜色不要太白。

window = this.activity.getWindow();
decorView = window.getDecorView();
// 设置状态栏颜色
window.setStatusBarColor(statusBarColorBefore23);

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

// 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 覆盖到状态栏上面。

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。

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 来调整位置。

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

因此 4.4 版本也要修改

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 版本也是要将内容往下,也要特殊考虑。

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;
}

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

statusBarColorBefore23 = statusBarColorAfter23 = 0;
isLightStatusBarAfter23 = true;

支持第三方 SDK 页面

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

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 创建好了后调用

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Android UI 测试 - Espresso

    Android UI 测试框架,在真机运行,相比手动测试,相当于把流程自动化了,并且自动监测结果。

    七适散人
  • RecyclerView 使用总结

    自定义类继承 RecyclerView.ItemDecoration,重写回调方法

    七适散人
  • Flutter 学习笔记4-构建布局示例

    一行中有三个子元素,其中第一列子元素本身又是一列,包含两行文字。需要占用大量空间,所以它必须包装在 Expanded widget 中。

    七适散人
  • 实习入职第二十天:从setRecyclerListener看listView回收机制

    关于这个  setRecyclerListener函数在解决   listView滑出屏幕(包括向上滑出和向下滑出)处理相关UI操作或者释放相关资源,真的很好用...

    wust小吴
  • 鸿洋AutoLayout代码分析(六):AutoAttr子类补充

    上一张,简单说明了 AutoAttr类的方法, 以及子类的实现,以及子类实现分类和理解 这里再简单补充一下

    dodo_lihao
  • 云开发实战分享|诗和远方:旅行小账本云开发

    最近沉迷小程序开发,发现了一款功能、界面、体验俱佳的小程序“旅行小账本”。着手做了个简约版——"旅行小账本"。效果比较满意,毕竟前后台一人单干。

    腾讯云开发TCB
  • android如何获取view在布局中的高度与宽度详解

    可能很多情况下,我们都会有在activity中获取view 的尺寸大小(宽度和高度)的需求。面对这种情况,很多同学立马反应:这么简单的问题,还用你说?你是不是傻...

    砸漏
  • Android自定义控件总结

    六月的雨
  • 微信小程序列表项滑动显示删除按钮

    微信小程序并没有提供列表控件,所以也没有iOS上惯用的列表项左滑删除的功能,SO只能自己干了。

    kklldog
  • Android中获取控件宽高的4种方法集合

    这个方法会被调用多次,在View初始化完毕后会调用,当Activity的窗口得到焦点和失去焦点都会被调用一次(Activity继续执行和暂停执行时)。

    砸漏

扫码关注云+社区

领取腾讯云代金券