Android 透明狀態(tài)欄(偽沉浸式)

4.4 以上要做所謂沉浸式,其實(shí)不是真正意義上的沉浸式,只是一種透明狀態(tài)欄。

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

適配 5.0 和 6.0 以上

應(yīng)用風(fēng)格如果是白色的,想把狀態(tài)欄也設(shè)置成白色的,會導(dǎo)致狀態(tài)欄上的圖標(biāo)文字看不見了,經(jīng)查詢發(fā)現(xiàn) 6.0 以上可以修改狀態(tài)欄圖標(biāo)文字風(fēng)格,可以改成黑的,但是 6.0 以下版本無解。體驗(yàn)了 QQ 瀏覽器,因?yàn)榫W(wǎng)頁大多都是純白的,在 6.0 的手機(jī)上狀態(tài)欄背景純白,圖標(biāo)文字改成黑的了,但在 5.1 的手機(jī)上圖標(biāo)文字沒法改,它是把背景做成灰色的了。

6.0 以下無法改狀態(tài)欄圖標(biāo)文字顏色,只能控制顏色不要太白。

window = this.activity.getWindow();
decorView = window.getDecorView();
// 設(shè)置狀態(tài)欄顏色
window.setStatusBarColor(statusBarColorBefore23);

6.0 以上可以根據(jù)狀態(tài)欄要變化的顏色來調(diào)整狀態(tài)欄圖標(biāo)文字的風(fēng)格。

// isLightStatusBarAfter23 控制是否更改狀態(tài)欄圖標(biāo)文字顏色
int flag = isLightStatusBarAfter23 ? View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR : View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
decorView.setSystemUiVisibility(flag);
window.setStatusBarColor(statusBarColorAfter23); // 設(shè)置狀態(tài)欄顏色

適配 4.4

4.4 版本需要透明狀態(tài)欄,將內(nèi)容往下移,然后再加一個(gè)和狀態(tài)欄一樣大小的 View 覆蓋到狀態(tài)欄上面。

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 上這么加狀態(tài)欄下面會有黑邊
//             ((ViewGroup)decorView.findViewById(android.R.id.content)).addView(view);
((ViewGroup)decorView).addView(view);

自動獲取布局背景色

如果沒指定顏色,自動獲取根 View 的背景,還找不到的話,再找第一個(gè)子 View,一開始遞歸找第一個(gè) View 的,感覺沒什么意義,調(diào)用者一般應(yīng)該明確傳顏色,不傳可能就是根 View 上設(shè)了背景之類。這就要考慮設(shè)的是顏色還是圖片。第一個(gè)子 View 是圖片還是普通 View 設(shè)了背景。因?yàn)槿绻菆D片,就不能設(shè)置狀態(tài)欄顏色或者蓋個(gè) View 上去,而是讓狀態(tài)欄透明,內(nèi)容往下,讓圖片透上去,當(dāng)然如果是子 View 的圖片,還不能 setFitsSystemWindows。

private boolean setStatusBarWithViewBg(View view, boolean isRootView) {
    Drawable drawable = view.getBackground();
    if (drawable != null) { // 設(shè)置了背景
        if (drawable instanceof ColorDrawable) {
            statusBarColorBefore23 = statusBarColorAfter23 = ((ColorDrawable) drawable).getColor();
            // ... 根據(jù)顏色去設(shè)置
        } else { // 背景是一張圖
            window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            view.setFitsSystemWindows(isRootView); // 如果第一個(gè)子View的圖片,要頂上去,不要下來,只有根 View 才下來

            // 如果是子 View,因?yàn)閳D片要上去,圖片里的內(nèi)容得下來,所以加個(gè) Padding
            view.setPadding(view.getPaddingLeft(),
                    view.getPaddingTop() +
                    (isRootView ? 0 :
                            getStatusBarHeight(activity),
                    view.getPaddingRight(), view.getPaddingBottom());
        }
        return true;
    } else {
        return false;
    }
}

4.4 版本和 setFitsSystemWindows 各種奇怪問題

setFitsSystemWindows 設(shè)置一次后再設(shè)置就沒用了,有時(shí)明明是 true 內(nèi)容又跑上去了,明明是 false 確跑下來了,反正多次調(diào)用這方法就各種問題。還遇到過 setFitsSystemWindows 導(dǎo)致內(nèi)容布局變化,如果不對每個(gè) Activity 配置一次 android:configChanges="screenSize|screenLayout",引起 onCreate 的多次調(diào)用。

所以盡量用 setPadding 來調(diào)整位置。

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); // 加個(gè)Tag,下次直接獲取該 View 更改顏色
    ((ViewGroup)decorView).addView(view);
}

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

項(xiàng)目中遇到一個(gè)問題,基類設(shè)置了一個(gè)默認(rèn)的狀態(tài)欄樣式,但某些 Activity 要自己單獨(dú)的樣式,又創(chuàng)建了一個(gè)對象,結(jié)果專門做沉浸的這個(gè)類被構(gòu)造了兩遍,導(dǎo)致 paddingTop 計(jì)算錯(cuò)誤。搞了兩遍,第二次 paddingTop 變成了兩個(gè)狀態(tài)欄高度加原來自己的 paddingTop,花了好長時(shí)間才排查出來。

所以解決方案就是基類構(gòu)造的對象作為屬性保存下來,然后子類就用父類的屬性。

狀態(tài)的重置

因?yàn)榭紤]同一個(gè) Activity 多次改變狀態(tài)欄顏色的情況,遇到的一個(gè)比較煩的問題是,許多狀態(tài)需要重置,不然就會影響下一次,而且如果設(shè)置圖片又改成顏色的,那么要考慮的更多,一會希望圖片內(nèi)容頂?shù)綘顟B(tài)欄下面,一會希望內(nèi)容能在狀態(tài)欄下面。

后來考慮將顏色和圖片的邏輯分開,因?yàn)橛袌D片時(shí)要重置的和只是改狀態(tài)欄顏色的不一樣,放一起如果只是改狀態(tài)欄顏色會走大量無意義的邏輯,當(dāng)然 4.4 版本也是要將內(nèi)容往下,也要特殊考慮。

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

還有最后每次設(shè)置完效果后要充值顏色值,以免影響下次使用

statusBarColorBefore23 = statusBarColorAfter23 = 0;
isLightStatusBarAfter23 = true;

支持第三方 SDK 頁面

如果是第三方的 SDK,跳轉(zhuǎn)的 Activity 是 SDK 里面的,可以用 ActivityLifecycleCallbacks,在 ActivityLifecycleCallbacks 里可以拿到 Activity 的實(shí)例,這里可以做沉浸。

public void onActivityStarted(Activity activity) {
    ...
    if (activity.getClass().getName().startsWith("第三方SDK包名前綴")) {
        new PseudoImmersiveModeManager(activity)
            .setStatusBarColor(Color.GRAY, Color.WHITE)
            .setIsLightStatusBarAfter23(true)
            .makeStatusBarImmersive();
    }
    ...
}

之所以不在 onActivityCreated 里調(diào)用,是因?yàn)殡m然 Activity 實(shí)例是有了,但是頁面還沒加載完成,獲取 rootView 時(shí)報(bào)空指針。

支持 DialogFragment

在 onCreateDialog 或 onViewCreated 的回調(diào)里,反正就是 Dialog 創(chuàng)建好了后調(diào)用

getDialog().getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
View view = getDialog().getWindow().getDecorView();
view.setPadding(view.getPaddingLeft, statusBarHeight, view.getPaddingRight, view.getPaddingBottom);

詳細(xì)代碼請見 Github 地址 ,下面分別是在 5.0 和 6.0 手機(jī)上的效果:

immersive5.gif

immersive6.gif
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容