在Android系統4.4以前,狀態欄的背景色和字體顏色都是不能改變的。但是4.4以后Google增加了改變狀態欄背景透明的方法,可以通過兩種方式來設置。
直接在Activity中設置Window屬性:
@Override
protected void onCreate(Bundle savedInstanceState) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
super.onCreate(savedInstanceState);
}
在xml的style文件中設置:
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowTranslucentStatus">true</item>
</style>
使用android:windowTranslucentStatus
屬性需要在res
目錄下新建values-v19
文件夾,style
文件要放在里面。
盡量使用第一種方式類實現,據說是第二種方式在某些國產手機上沒有效果。
兩種方式都是讓狀態背景欄透明。但是在實際測試后發現,在android4.4系統上狀態欄的背景是漸變半透明。而在android5.0+的系統上又有所不同又有差異:
在Genymotion模擬器、vivo、nexus6p的android5.0+系統上是半透明。
在xiaomi、oppo、huawei、leshi等5.0+系統是全透明的。
從效果圖也可以看出,上面兩種方式使得內容區域延伸到狀態欄下面去了。我們可以添加
android:fitsSystemWindows
來避免這樣的情況。在Activity的根布局文件中添加
android:fitsSystemWindows="true"
或者在主題style文件中添加
<item name="android:fitsSystemWindows">true</item>
上圖為了方便觀察狀態欄的背景顏色,就將Activity的布局文件的背景色設置成了紅色。可以看出Toolbar所在的內容區域沒有沉浸到狀態欄下面了,并且狀態欄的背景顏色還是之前一樣,只是將內容區域向下偏移了狀態欄高度的距離。這是因為
fitsSystemWindows
屬性使得布局的paddingTop
被重新改寫了(paddingTop
增加了狀態欄的高度)。要實現沉浸式的狀態欄,其實就是狀態欄的背景顏色和Toolbar的顏色一樣。那么我們將Activity的背景色改為Toolbar紫色(內容區域的背景顏色設置為白色)看下效果:
除了通過
fitsSystemWindows
這個屬性,我們可以自己給在Toolbar的上方添加一個和狀態欄高度一樣的Veiw,并將這個View的背景色設置成和Toolbar的背景色一樣。也可以直接給Toobar設置paddingTop
等于狀態欄高度值。為了方便,我一般是采用重寫Toolbar,改寫它的
paddingTop
值來實現。
/**
* Created by xiaoyanger on 2017/3/1.
* 沉浸式、版本兼容的Toolbar,狀態欄透明.
*/
public class CompatToolbar extends Toolbar {
public CompatToolbar(Context context) {
this(context, null);
}
public CompatToolbar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CompatToolbar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setup();
}
public void setup() {
int compatPadingTop = 0;
// android 4.4以上將Toolbar添加狀態欄高度的上邊距,沉浸到狀態欄下方
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
compatPadingTop = getStatusBarHeight();
}
this.setPadding(getPaddingLeft(), getPaddingTop() + compatPadingTop, getPaddingRight(), getPaddingBottom());
}
public int getStatusBarHeight() {
int statusBarHeight = 0;
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
statusBarHeight = getResources().getDimensionPixelSize(resourceId);
}
Log.d("CompatToolbar", "狀態欄高度:" + px2dp(statusBarHeight) + "dp");
return statusBarHeight;
}
public float px2dp(float pxVal) {
final float scale = getContext().getResources().getDisplayMetrics().density;
return (pxVal / scale);
}
}
需要注意的是,網上有很多文章說狀態欄的高度是25dp
,但實際測試后發現并不是所有的機型都是25dp
。使用getStatusBarHeight()
可以準確的獲取狀態欄的高度值,看下獲取到高度的日志:
各個機型獲取到的狀態欄高度很多都不一樣,所以最好還是重寫Toolbar,動態獲取到系統狀態欄的高度來改寫它的上邊距。
國產的大部分手機在android5.0+的系統都將原生的半透明狀態欄改成了全透明,因此通過上面的方式基本達到了沉浸式體驗。
其實在原生android5.0+系統上可以通過這個方法使狀態欄背景色完全透明:
@Override
protected void onCreate(Bundle savedInstanceState) {
// 5.0以上系統狀態欄透明
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.TRANSPARENT);
}
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
可以將上面兩個方法封裝到
BaseActivity
中,以便在各個界面直接調用:
protected void setTranslucentStatus() {
// 5.0以上系統狀態欄透明
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.TRANSPARENT);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
}
通過setTranslucentStatus()
方法,4.4的系統狀態欄背景色漸變半透明,絕大多數5.0+的系統狀態欄背景色全透明(測試時發現有部分華為手機狀態欄背景是淺紫色,估計是定制系統給修改了)。
需要注意的是,不管是<item name="android:windowTranslucentStatus">true</item>
還是setTranslucentStatus()
方法,這兩種方式都會使這個設置<itemname="colorPrimaryDark">@color/colorPrimaryDark</item>
不會有任何效果。
狀態欄的背景色按照上面的方式適配基本上滿足沉浸式體驗,但是有時候會遇到這樣的需求,狀態欄和Toolbar背景色都是白底黑字,比如簡書消息界面:
狀態欄的白色背景可以通過上面的方式來實現,但是修改狀態欄的文字和圖標顏色就比較麻煩了,原生的android系統只有在6.0+才有官方的api,但是在MIUI和FlymeUI提供了相應的方法。
/**
* 設置Android狀態欄的字體顏色,狀態欄為亮色的時候字體和圖標是黑色,狀態欄為暗色的時候字體和圖標為白色
*
* @param dark 狀態欄字體和圖標是否為深色
*/
protected void setStatusBarFontDark(boolean dark) {
// 小米MIUI
try {
Window window = getWindow();
Class clazz = getWindow().getClass();
Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");
Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");
int darkModeFlag = field.getInt(layoutParams);
Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);
if (dark) { //狀態欄亮色且黑色字體
extraFlagField.invoke(window, darkModeFlag, darkModeFlag);
} else { //清除黑色字體
extraFlagField.invoke(window, 0, darkModeFlag);
}
} catch (Exception e) {
e.printStackTrace();
}
// 魅族FlymeUI
try {
Window window = getWindow();
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 (dark) {
value |= bit;
} else {
value &= ~bit;
}
meizuFlags.setInt(lp, value);
window.setAttributes(lp);
} catch (Exception e) {
e.printStackTrace();
}
// android6.0+系統
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (dark) {
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
}
}
狀態欄和Toobar白底黑字只能適配到android6.0+以及小米和魅族手機,其他手機的只能調整UI設計了。可以看一下簡書和掘金,在小米、魅族和android6.0+的系統上都是白底黑字,但是在其他的系統上一般是將狀態欄的背景色設置為淺灰色。
其實狀態欄顯示的顏色是灰色,但是有多種方式來實現,參考下簡書、掘金、今日頭條,他們都使用了右滑返回上一界面,在滑動過程中就能看出狀態背景色的實現。
簡書的效果可以不用上面的方式使內容區域沉浸到狀態欄下面,直接使用的
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
直接修改的狀態欄的顏色。
掘金的狀態欄是半透明的,內容區域已經沉浸到狀態欄下方,可以使用上面的方法,對于不是android6.0+、MIUI、FlymeUI的系統通過window.setStatusbarColor
方法將狀態欄顏色改為半透明的灰色。
今日頭條的效果可以采用上面的方法實現沉浸式,狀態欄全透明,Toolbar沒有增加paddingTop
,而是在第二個界面中狀態欄的下方(Toolbar上方)加了一塊同狀態欄高度一樣的View,顏色為灰色。
其實,從上面三個app的狀態欄適配效果來看,要達到真正的沉浸式,今日頭條的效果要好一些。但是今日頭條并沒有在各個機型上適配狀態欄的字體和圖標顏色。
下面自己來實現不同機型的狀態欄背景色和字體圖標顏色的適配。
首先,新建一個CompatStartusbarActivity
,繼承自BaseActivity
:
public class CompatStatusBarActivity extends BaseActivity {
private FrameLayout mFrameLayoutContent;
private View mViewStatusBarPlace;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.setContentView(R.layout.activity_compat_status_bar);
mViewStatusBarPlace = findViewById(R.id.view_status_bar_place);
mFrameLayoutContent = (FrameLayout) findViewById(R.id.frame_layout_content_place);
ViewGroup.LayoutParams params = mViewStatusBarPlace.getLayoutParams();
params.height = getStatusBarHeight();
mViewStatusBarPlace.setLayoutParams(params);
}
@Override
public void setContentView(@LayoutRes int layoutResID) {
View contentView = LayoutInflater.from(this).inflate(layoutResID, null);
mFrameLayoutContent.addView(contentView);
}
/**
* 設置沉浸式狀態欄
*
* @param fontIconDark 狀態欄字體和圖標顏色是否為深色
*/
protected void setImmersiveStatusBar(boolean fontIconDark, int statusBarColor) {
setTranslucentStatus();
if (fontIconDark) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|| OsUtil.isMIUI()
|| OsUtil.isFlyme()) {
setStatusBarFontIconDark(true);
} else {
if (statusBarColor == Color.WHITE) {
statusBarColor = 0xffcccccc;
}
}
}
setStatusBarPlaceColor(statusBarColor);
}
private void setStatusBarPlaceColor(int statusColor) {
if (mViewStatusBarPlace != null) {
mViewStatusBarPlace.setBackgroundColor(statusColor);
}
}
public int getStatusBarHeight() {
int statusBarHeight = 0;
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
statusBarHeight = getResources().getDimensionPixelSize(resourceId);
}
return statusBarHeight;
}
/**
* 設置狀態欄透明
*/
private void setTranslucentStatus() {
// 5.0以上系統狀態欄透明
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.TRANSPARENT);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
}
/**
* 設置Android狀態欄的字體顏色,狀態欄為亮色的時候字體和圖標是黑色,狀態欄為暗色的時候字體和圖標為白色
*
* @param dark 狀態欄字體是否為深色
*/
private void setStatusBarFontIconDark(boolean dark) {
// 小米MIUI
try {
Window window = getWindow();
Class clazz = getWindow().getClass();
Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");
Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");
int darkModeFlag = field.getInt(layoutParams);
Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);
if (dark) { //狀態欄亮色且黑色字體
extraFlagField.invoke(window, darkModeFlag, darkModeFlag);
} else { //清除黑色字體
extraFlagField.invoke(window, 0, darkModeFlag);
}
} catch (Exception e) {
e.printStackTrace();
}
// 魅族FlymeUI
try {
Window window = getWindow();
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 (dark) {
value |= bit;
} else {
value &= ~bit;
}
meizuFlags.setInt(lp, value);
window.setAttributes(lp);
} catch (Exception e) {
e.printStackTrace();
}
// android6.0+系統
// 這個設置和在xml的style文件中用這個<item name="android:windowLightStatusBar">true</item>屬性是一樣的
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (dark) {
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
}
}
}
布局文件activity_compat_status_bar.xml
:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.xiao.compatstatusbar.MainActivity">
<View
android:id="@+id/view_status_bar_place"
android:layout_width="match_parent"
android:layout_height="25dp"/>
<FrameLayout
android:id="@+id/frame_layout_content_place"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
setImmersiveStatusBar
方法中中判斷了不能修改狀態欄字體和圖標顏色則將白色的狀態欄底色改成灰色,能修改狀態欄字體和圖標的顏色則改為給定的顏色statusBarColor
。使用的時候直接調用這個方法即可。
下面是使用示例:
MainActivity
繼承自CompatStatusBarActivity
:
public class MainActivity extends CompatStatusBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar_main);
int color = 0xffaa66cc;
toolbar.setBackgroundColor(color);
setImmersiveStatusBar(false, color);
}
public void go(View view) {
startActivity(new Intent(this, NextActivity.class));
}
}
布局文件activity_main.xml
:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.xiao.compatstatusbar.MainActivity">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar_main"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@color/colorPrimary">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="標題"
android:textColor="@android:color/white"
android:textSize="18sp"/>
</LinearLayout>
</android.support.v7.widget.Toolbar>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#99FF4081"
android:onClick="go"
android:text="頁面1"
android:textSize="44sp"/>
</LinearLayout>
NextActivity
同樣繼承自CompatStatusBarActivity
:
public class NextActivity extends CompatStatusBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_next);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar_next);
int color = 0xffffffff;
toolbar.setBackgroundColor(color);
setImmersiveStatusBar(true, color);
}
}
布局文件activity_next.xml
:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.xiao.compatstatusbar.MainActivity">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar_next"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@color/colorPrimary">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="標題"
android:textColor="#353535"
android:textSize="18sp"/>
</LinearLayout>
</android.support.v7.widget.Toolbar>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#99FF4081"
android:text="頁面2"
android:textSize="44sp"/>
</LinearLayout>
最終適配效果:
源碼:https://github.com/xiaoyanger0825/CompatStatusBar