讀完本文大約需要20分鐘脆淹。
本文將會(huì)教給大家如何從0到1優(yōu)雅地實(shí)現(xiàn)沉浸式狀態(tài)欄的效果巾钉,包括設(shè)置狀態(tài)欄的顏色规脸、狀態(tài)欄透明和狀態(tài)欄的文字顏色坯约,并可以適配啟動(dòng)頁(yè)和劉海屏等等。
1. 概述
現(xiàn)在市面上絕大多數(shù)APP都實(shí)現(xiàn)了沉浸式狀態(tài)欄的效果莫鸭,該效果可以極大地提升用戶的使用體驗(yàn)闹丐。但是,APP默認(rèn)的情況下只是設(shè)定一個(gè)固定的狀態(tài)欄顏色被因,那么就需要我們通過(guò)代碼去動(dòng)態(tài)修改狀態(tài)欄顏色的卿拴。那么,如何去實(shí)現(xiàn)呢梨与?
首先來(lái)說(shuō)堕花,從Android 4.4開(kāi)始才能實(shí)現(xiàn)沉浸式狀態(tài)欄的,所以如果您的APP也支持Android 4.4以下版本粥鞋,那還需要對(duì)Android 4.4以下版本做“不支持沉浸式狀態(tài)欄”處理缘挽。
那么,從Android 4.4開(kāi)始陷虎,大概可以分成三個(gè)階段來(lái)實(shí)現(xiàn)沉浸式狀態(tài)欄:
-
Android4.4(API 19) - Android 5.0(API 21):這個(gè)階段的實(shí)現(xiàn)方式為:通過(guò)
FLAG_TRANSLUCENT_STATUS
設(shè)置狀態(tài)欄為透明并且為全屏模式到踏,然后通過(guò)添加一個(gè)與StatusBar一樣大小的View,將View的背景設(shè)置為要設(shè)置的顏色尚猿,從而實(shí)現(xiàn)沉浸式窝稿。 -
Android 5.0(API 21) - Android 6.0(API 23): 從Android 5.0開(kāi)始,加入了一個(gè)重要的屬性
android:statusBarColor
和方法setStatusBarColor()
凿掂,通過(guò)這個(gè)方法我們就可以輕松實(shí)現(xiàn)沉浸式狀態(tài)欄伴榔。但是在Android 6.0以下版本官方不支持設(shè)置狀態(tài)欄的文字和圖標(biāo)顏色,目前只有小米和魅族的ROM提供了支持庄萎。 - Android 6.0(API 23)以上版本:其實(shí)Android 6.0以上的實(shí)現(xiàn)方式和Android 5.0+ 是一樣的踪少,區(qū)別是從Android 6.0開(kāi)始,官方支持改變狀態(tài)欄的文字和圖標(biāo)的顏色糠涛。
2. 實(shí)現(xiàn)方案
2.1 前期工作
為了實(shí)現(xiàn)沉浸式狀態(tài)欄的效果援奢,我們需要做一些前期工作,如下:
2.1.1 修改應(yīng)用主題
為了更好地演示沉浸式狀態(tài)欄的效果忍捡,我們修改應(yīng)用的主題集漾,使其不要顯示Android默認(rèn)的標(biāo)題欄切黔。
打開(kāi)styles.xml
文件,可以改成如下代碼具篇,并且將colorPrimary
等顏色設(shè)置刪掉:
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
</style>
2.1.2 獲取狀態(tài)欄高度
新建一個(gè)StatusBarUtils.java
文件纬霞,在里面添加一個(gè)獲取狀態(tài)欄高度的方法,以后我們都會(huì)用的到這個(gè)方法驱显。之后我們的核心代碼也在這個(gè)工具類里實(shí)現(xiàn)诗芜。代碼如下:
public class StatusBarUtils {
public static int getHeight(Context context) {
int statusBarHeight = 0;
try {
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen",
"android");
if (resourceId > 0) {
statusBarHeight = context.getResources().getDimensionPixelSize(resourceId);
}
} catch (Exception e) {
e.printStackTrace();
}
return statusBarHeight;
}
}
下面我們就按照從高版本到低版本的順序來(lái)講解如何更優(yōu)雅地實(shí)現(xiàn)沉浸式狀態(tài)欄。
2.2 Android 5.0+
2.2.1 設(shè)置狀態(tài)欄顏色
在StatusBarUtils
類里添加如下方法埃疫,實(shí)現(xiàn)設(shè)置狀態(tài)欄顏色的功能:
public static void setColor(@NonNull Window window, @ColorInt int color) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
window.setStatusBarColor(color);
}
}
修改狀態(tài)欄顏色的功能其實(shí)就是對(duì)Window
進(jìn)行操作伏恐,而該Window可以是Activity或Dialog等持有的Window,所以我們就封裝了一個(gè)傳遞Window的方法熔恢。
為了便于對(duì)Activity直接操作脐湾,可以再增加一個(gè)如下方法:
public static void setColor(Context context, @ColorInt int color) {
if (context instanceof Activity) {
setColor(((Activity) context).getWindow(), color);
}
}
下面測(cè)試一下叙淌,新建一個(gè)Activity愁铺,將其布局背景設(shè)置為某個(gè)顏色,例如主題色茵乱,布局代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/colorPrimaryDark">
</LinearLayout>
在沒(méi)設(shè)置狀態(tài)欄顏色時(shí)茂洒,展示效果如下:
接下來(lái)在Activity的onCreate()
方法里調(diào)用設(shè)置狀態(tài)欄顏色的方法督勺,代碼如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_color);
StatusBarUtils.setColor(this, getResources().getColor(R.color.colorPrimaryDark));
}
展示效果如下:
這樣狀態(tài)欄和內(nèi)容的顏色就可以一樣啦!
2.2.2 設(shè)置狀態(tài)欄文字顏色
下面我們把背景和狀態(tài)欄顏色改成純綠色(#00FF00)斤贰,看看效果:
發(fā)現(xiàn)顏色好亮啊,狀態(tài)欄里的文字都看不清了荧恍。在這種情況下送巡,我們是可以將狀態(tài)欄文字的顏色改成深色的,官方也僅支持設(shè)置狀態(tài)欄文字和圖標(biāo)的深色模式和淺色模式次氨,但是官方僅在Android 6.0以上版本提供支持摘投。設(shè)置代碼如下:
private static void setTextDark(Window window, boolean isDark) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
View decorView = window.getDecorView();
int systemUiVisibility = decorView.getSystemUiVisibility();
if (isDark) {
decorView.setSystemUiVisibility(systemUiVisibility | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
} else {
decorView.setSystemUiVisibility(systemUiVisibility & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
}
}
同樣再增加一個(gè)對(duì)Activity的支持:
public static void setTextDark(Context context, boolean isDark) {
if (context instanceof Activity) {
setTextDark(((Activity) context).getWindow(), isDark);
}
}
為了能夠根據(jù)狀態(tài)欄背景顏色的深淺而自動(dòng)設(shè)置文字的顏色,我們?cè)傩略鲆粋€(gè)判斷顏色深淺的方法:
public static boolean isDarkColor(@ColorInt int color) {
return ColorUtils.calculateLuminance(color) < 0.5;
}
然后在setColor()
方法里新增一行設(shè)置狀態(tài)欄文字顏色的代碼武花,如下:
public static void setColor(@NonNull Window window, @ColorInt int color) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
window.setStatusBarColor(color);
setTextDark(window, !isDarkColor(color));
}
}
運(yùn)行一下体箕,就可以看到效果了:
上面是Android 6.0以上版本的實(shí)現(xiàn)累铅,那么對(duì)于Android 6.0以下的手機(jī)怎么辦呢站叼?目前Android 5.0-6.0的手機(jī)只有小米MIUI和魅族Flyme系統(tǒng)提供了支持尽楔。小米MIUI的設(shè)置方法如下:
private static void setMIUIDark(Window window, boolean isDark) {
try {
Class<? extends Window> clazz = window.getClass();
int darkModeFlag;
Class<?> layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");
Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");
darkModeFlag = field.getInt(layoutParams);
Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);
extraFlagField.invoke(window, isDark ? darkModeFlag : 0, darkModeFlag);
} catch (Exception e) {
e.printStackTrace();
}
}
魅族Flyme的設(shè)置方法如下:
private static void setFlymeDark(Window window, boolean isDark) {
if (window != null) {
try {
WindowManager.LayoutParams lp = window.getAttributes();
Field darkFlag = WindowManager.LayoutParams.class
.getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON");
Field meizuFlags = WindowManager.LayoutParams.class
.getDeclaredField("meizuFlags");
darkFlag.setAccessible(true);
meizuFlags.setAccessible(true);
int bit = darkFlag.getInt(null);
int value = meizuFlags.getInt(lp);
if (isDark) {
value |= bit;
} else {
value &= ~bit;
}
meizuFlags.setInt(lp, value);
window.setAttributes(lp);
} catch (Exception e) {
e.printStackTrace();
}
}
}
然后在setTextDark()
方法中添加如下代碼:
private static void setTextDark(Window window, boolean isDark) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
View decorView = window.getDecorView();
int systemUiVisibility = decorView.getSystemUiVisibility();
if (isDark) {
decorView.setSystemUiVisibility(systemUiVisibility | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
} else {
decorView.setSystemUiVisibility(systemUiVisibility & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
switch (OSUtils.getRomType()) {
case MIUI:
setMIUIDark(window, isDark);
break;
case Flyme:
setFlymeDark(window, isDark);
break;
default:
}
}
}
Android 6.0以下運(yùn)行效果同上玛荞,我就不給大家截圖了呕寝。
2.2.3 設(shè)置狀態(tài)欄透明
當(dāng)我們APP的背景是一張圖片時(shí),未設(shè)置沉浸式狀態(tài)欄的效果如下:
這時(shí)一般我們需要將圖片頂?shù)綘顟B(tài)欄里客蹋,也就是整個(gè)內(nèi)容布局頂?shù)綘顟B(tài)欄里讶坯,并設(shè)置狀態(tài)欄的顏色透明竟坛,才能實(shí)現(xiàn)沉浸式狀態(tài)欄的效果。
那么涎跨,在我們的StatusBarUtils
類里添加如下代碼:
public static void setTransparent(@NonNull Window window) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
window.setStatusBarColor(Color.TRANSPARENT);
}
}
同樣針對(duì)Activity隅很,增加如下方法:
public static void setTransparent(Context context) {
if (context instanceof Activity) {
setTransparent(((Activity) context).getWindow());
}
}
最后在Activity的onCreate()
方法里設(shè)置一下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_transparent);
StatusBarUtils.setTransparent(this);
}
運(yùn)行叔营,顯示效果如下:
針對(duì)這種情況,大家在實(shí)現(xiàn)布局時(shí)不要忘了把狀態(tài)欄的高度也考慮進(jìn)去畜挥。
以上就是Android 5.0以上沉浸式狀態(tài)欄的實(shí)現(xiàn)了蟹但。
2.3 Android 4.4+
下面針對(duì)Android 4.4-5.0的手機(jī)進(jìn)行實(shí)現(xiàn)谭羔。實(shí)現(xiàn)原理是將內(nèi)容布局設(shè)為全屏瘟裸,然后在布局的頂部添加一個(gè)和狀態(tài)欄一樣高度的View,將該View的背景設(shè)置成我們想要的顏色兼搏。當(dāng)需要將狀態(tài)欄設(shè)置純顏色時(shí)向族,為了和Android 5.0以上版本保持一致棠绘,我們對(duì)內(nèi)容布局的上邊設(shè)置一個(gè)padding氧苍,大小為狀態(tài)欄的高度泛范。
為了能復(fù)用這個(gè)View罢荡,我們新增一個(gè)自定義的ID,在values
文件夾下新建ids.xml
文件惭缰,新增代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="fake_status_bar_view" type="id"/>
</resources>
然后在StatusBarUtils
類里添加如下代碼:
private static final int FAKE_STATUS_BAR_VIEW_ID = R.id.fake_status_bar_view;
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public static void setColor(@NonNull Window window, @ColorInt int color,
boolean isTransparent) {
Context context = window.getContext();
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
ViewGroup decorView = (ViewGroup) window.getDecorView();
View contentView = decorView.findViewById(android.R.id.content);
if (contentView != null) {
contentView.setPadding(0, isTransparent ? 0 : getHeight(context), 0, 0);
}
View fakeStatusBarView = decorView.findViewById(FAKE_STATUS_BAR_VIEW_ID);
if (fakeStatusBarView != null) {
fakeStatusBarView.setBackgroundColor(color);
if (fakeStatusBarView.getVisibility() == View.GONE) {
fakeStatusBarView.setVisibility(View.VISIBLE);
}
} else {
// 繪制一個(gè)和狀態(tài)欄一樣高的矩形
View statusBarView = new View(context);
FrameLayout.LayoutParams layoutParams =
new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
getHeight(context));
statusBarView.setLayoutParams(layoutParams);
statusBarView.setBackgroundColor(color);
statusBarView.setId(FAKE_STATUS_BAR_VIEW_ID);
decorView.addView(statusBarView);
}
}
2.3.1 設(shè)置狀態(tài)欄顏色
在設(shè)置純顏色時(shí),我們還需要將該顏色與黑色進(jìn)行1:1的混合昂羡。為什么要這么設(shè)置呢?因?yàn)闋顟B(tài)欄的文字和圖標(biāo)顏色默認(rèn)是白色的怨愤,并且在Android 5.0以下是不能修改的蛹批,所以如果修改成較淺的顏色般眉,就會(huì)導(dǎo)致?tīng)顟B(tài)欄文字看不清的現(xiàn)象,因此做一個(gè)比較暗的浮層效果更好一些柿汛。
那么將setColor()
方法改成如下代碼:
public static void setColor(@NonNull Window window, @ColorInt int color) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
window.setStatusBarColor(color);
setTextDark(window, !isDarkColor(color));
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
setColor(window, ColorUtils.blendARGB(Color.TRANSPARENT, color, 0.5f), false);
}
}
展示效果如下:
2.3.2 設(shè)置狀態(tài)欄透明
在設(shè)置狀態(tài)欄透明時(shí),為了也能清楚地看清狀態(tài)欄的文字项玛,我們直接設(shè)置狀態(tài)欄的顏色為50%透明度的黑色襟沮。
于是开伏,修改setTransparent()
方法如下:
public static void setTransparent(@NonNull Window window) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
window.setStatusBarColor(Color.TRANSPARENT);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
setColor(window, 0x80000000, true);
}
}
展示效果如下:
這樣固灵,Android 4.4以上的手機(jī)就適配完畢了巫玻。
3. 特殊場(chǎng)景
現(xiàn)在來(lái)針對(duì)一些特殊場(chǎng)景進(jìn)行適配,比如啟動(dòng)頁(yè)熄诡、劉海屏和彈窗等等徒扶。
3.1 啟動(dòng)頁(yè)
對(duì)于啟動(dòng)頁(yè),一般都會(huì)把狀態(tài)欄收上去屿良,這需要適配劉海屏惫周,否則劉海區(qū)域會(huì)顯示黑的一片递递。其實(shí)Android P以上提供了適配劉海屏的方法,在啟動(dòng)頁(yè)Activity添加如下代碼:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
StatusBarUtils.setTransparent(this);
// 適配劉海屏
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
WindowManager.LayoutParams layoutParams = getWindow().getAttributes();
layoutParams.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
getWindow().setAttributes(layoutParams);
}
}
但是一些手機(jī)廠商的劉海屏手機(jī)系統(tǒng)版本是低于Android P的贰逾,不過(guò)也都提供了適配的方法疙剑。適配方式是在AndroidManifest.xml
文件里的application
標(biāo)簽下添加如下代碼:
<!-- 允許繪制到小米劉海屏機(jī)型的劉海區(qū)域 -->
<meta-data
android:name="notch.config"
android:value="portrait" />
<!-- 允許繪制到華為劉海屏機(jī)型的劉海區(qū)域 -->
<meta-data
android:name="android.notch_support"
android:value="true" />
<!-- 允許繪制到oppo言缤、vivo劉海屏機(jī)型的劉海區(qū)域 -->
<meta-data
android:name="android.max_aspect"
android:value="2.2" />
另外禁灼,對(duì)于Android 5.0以下的手機(jī)弄捕,適配完劉海屏后會(huì)在頂部多一塊黑色半透明的View,那我們將其改成全透明的皮璧,修改onCreate()
方法如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
StatusBarUtils.setTransparent(this);
// 適配劉海屏
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
WindowManager.LayoutParams layoutParams = getWindow().getAttributes();
layoutParams.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
getWindow().setAttributes(layoutParams);
}
// 適配Android 4.4
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
StatusBarUtils.setColor(getWindow(), Color.TRANSPARENT, true);
}
}
這樣,啟動(dòng)頁(yè)的效果就適配好了譬猫,效果如下:
3.2 彈窗
對(duì)于彈窗染服,只有全屏彈窗才能修改狀態(tài)欄文字的顏色叨恨。詳見(jiàn)Android非全屏的Window無(wú)法設(shè)置SYSTEM_UI_FLAG_LIGHT_STATUS_BAR問(wèn)題分析。
好啦痢毒,以上就是全部代碼了蚕甥,同時(shí)我也放在GitHub上了菇怀,詳見(jiàn):https://github.com/jimmysuncpt/ImmersionStatusBar。