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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,384评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,845评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,148评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,640评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,731评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,712评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,703评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,473评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,915评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,227评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,384评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,063评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,706评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,302评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,531评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,321评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,248评论 2 352

推荐阅读更多精彩内容