美文网首页UI我们一起学AndroidAndroid开发
Android 透明状态栏(伪沉浸式)

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

作者: 三流之路 | 来源:发表于2018-03-28 17:48 被阅读96次

    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

    相关文章

      网友评论

        本文标题:Android 透明状态栏(伪沉浸式)

        本文链接:https://www.haomeiwen.com/subject/ezjucftx.html