什么是沉浸式
可以參考SystemBarTint
狀態(tài)欄 —— StausBar
通過colorPrimaryDark
屬性修改狀態(tài)欄,只適用于5.0以后
你可以通過修改主題下的屬性闻察,來改變狀態(tài)欄的顏色
<style name="AppTheme" parent="Theme.AppCompat.Light.[圖片上傳中...(windowTranslucentStatus_4.4.png-3332a0-1511580222129-0)]
">
<item name="colorPrimaryDark">@color/red</item>
</style>
版本 | 說明 | 對比 |
---|---|---|
4.3 | colorPrimaryDark屬性 | |
4.4 | colorPrimaryDark屬性 | |
5.0 | colorPrimaryDark屬性 |
那么如何兼容4.4呢?
通過android:windowTranslucentStatus
屬性兼容4.4
在v19\styles.xml
中給主題添加以下屬性
<item name="android:windowTranslucentStatus">true</item>
版本 | 說明 | 對比 |
---|---|---|
4.3 | android:windowTranslucentStatus屬性 | |
4.4 | android:windowTranslucentStatus屬性 | |
5.0 | android:windowTranslucentStatus屬性 |
如果要用此方法兼容4.4版本勤众,則許創(chuàng)建v21\styles.xml
,給5.0以后的版本提供以下主題:
<item name="colorPrimaryDark">@color/red</item>
否則匾乓,5.0以上會沿用v19\styles.xml
中定義的主題屬性欢唾,效果如下圖
如果讓主題去掉ActionBar,會是怎么樣呢借宵?
NoActionBar
如果設置成NoActionBar的主題風格,把布局改成如下:
<?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">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/yellow"
android:gravity="top|center_horizontal"
android:text="@string/content" />
</LinearLayout>
版本 | 說明 | 對比 |
---|---|---|
4.3 | NoActionBar | |
4.4 | NoActionBar | |
5.0 | NoActionBar |
從結果可以看出4.4版本界面的內容會覆蓋StatusBar矾削,而5.0以上則不會出現壤玫,那為什么會出現這種情況?通過setContentView源碼分析了解哼凯,在5.0版本后欲间,PhoneWindow
類的installDecor
方法中調用了:
// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeOptionalFitsSystemWindows();
這個方法是在ViewGroup中實現的:
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
children[i].makeOptionalFitsSystemWindows();
}
從這個實現來看,就是遍歷DecorView的子類添加fitsSystemWindows
標記断部,這個標記是用來干嘛的呢猎贴?
fitsSystemWindow解決內容布局和StatusBar重合
fitsSystemWindow 如果設置為true,就是組件都在屏幕內蝴光,但是不包括statusBar
嘱能。設置成false后,整個屏幕都可以放置組件虱疏,沒有statusBar
和window
之分
這樣,如果在布局中給根布局加上fitsSystemWindow
屬性苏携,如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
在4.4系統上會顯示成下面這樣:
并沒有達到我們想要的效果做瞪,雖然能和StatusBar
區(qū)域分隔開,但是StatusBar
的顏色卻變?yōu)榱税咨叶常@是為什么呢装蓬?雖然我們通過fitsSystemWindow
屬性讓內容部分與StatusBar
隔開一定的間距,但是由于4.4中我們給StatusBar
設置成了透明色纱扭,所以StatusBar
區(qū)域顯示了根布局的背景顏色牍帚。
修改根布局背景顏色達到沉浸式效果
因此修改根布局的背景顏色為黃色,則能達到我們想要的效果:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/yellow"
android:fitsSystemWindows="true">
這樣子雖然能達到沉浸式效果乳蛾,但是背景顏色卻固定了暗赶,而且每個界面的根布局都得添加fitsSystemWindow
屬性很麻煩鄙币,如何統一有效得兼容4.4以上的系統來達到沉浸式效果呢?
最終兼容方案
針對5.0以上的系統
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().setStatusBarColor(barColor);
}
針對4.4到5.0之間的系統
- 將
StatusBar
設置為透明
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
- 獲取到根布局mContentRoot蹂随,設置mContentRoot的FitsSystemWindows
mContentRoot = findViewById(android.R.id.content);
mContentRoot.setFitsSystemWindows(true);
- 創(chuàng)建一個和
StatusBar
高度一樣的幀布局作為假的StatusBar
十嘿,修改其背景顏色達到沉浸效果
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
getStatusBarHeight());
FrameLayout frameLayout = new FrameLayout(context);
frameLayout.setLayoutParams(lp);
frameLayout.setBackgroundColor(barColor);
mContentRoot.addView(frameLayout);
如何獲取StatusBar的高度
這個高度是在frameworks/base/core/res/res/values/dimens.xml里面定義的:
<!-- Height of the status bar -->
<dimen name="status_bar_height">25dip</dimen>
這個資源文件是系統的,不會生成在項目的R文件中岳锁,所以在項目中通過getDimen
是找不到這個資源的绩衷,只能在系統編譯生成的資源文件中找到這個資源。保存在out/target/common/R/com/android/internal/R.java
下激率,
- 可以通過反射拿到想要的資源的值
public int getSystemDimenPx(String field) {
int result = 0;
try {
Class clz = Class.forName("com.android.internal.R$dimen");
Object obj = clz.newInstance();
int resourceId = Integer.parseInt(clz.getField(field).get(obj).toString());
if (resourceId > 0)
result = getResources().getDimensionPixelSize(resourceId);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
- 可以通過getIdentifier方法獲得系統資源id
int result = 0;
int resourceId = Resources.getSystem().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0)
result = getResources().getDimensionPixelSize(resourceId);
導航欄 —— NavigationBar
和狀態(tài)欄的實現類似
最終兼容方案
針對5.0以上的系統
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().setNavigationBarColor(barColor);
}
針對4.4到5.0之間的系統
- 將
NavagationBar
設置為透明
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
- 判斷是否存在導航欄咳燕,有些手機沒有導航欄,通過物理屏幕減去內容屏幕的相應寬高判斷
private boolean hasNavigationBar(Activity activity) {
WindowManager windowManager = activity.getWindowManager();
Display d = windowManager.getDefaultDisplay();
// 真實物理屏幕
DisplayMetrics realDisplayMetrics = new DisplayMetrics();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
d.getRealMetrics(realDisplayMetrics);
}
int realHeight = realDisplayMetrics.heightPixels;
int realWidth = realDisplayMetrics.widthPixels;
// 屏幕內容高度
DisplayMetrics displayMetrics = new DisplayMetrics();
d.getMetrics(displayMetrics);
int displayHeight = displayMetrics.heightPixels;
int displayWidth = displayMetrics.widthPixels;
// 防止橫屏乒躺,寬高都判斷
return (realWidth - displayWidth) > 0 || (realHeight - displayHeight) > 0;
}
- 如果存在導航欄招盲,則在導航欄對應的位置上創(chuàng)建一個和
NavagationBar
高度一樣的幀布局作為假的NavagationBar
,修改其背景顏色達到沉浸效果
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
getNavigationBarHeight());
lp.gravity = Gravity.BOTTOM;
FrameLayout frameLayout = new FrameLayout(activity);
frameLayout.setLayoutParams(lp);
frameLayout.setBackgroundColor(barColor);
mContentRoot.addView(frameLayout);
最終效果如圖:
擴展 —— 如何實現圖片全屏沉浸式效果
結合之前分析的如何實現顏色沉浸是效果聪蘸,得出:
- 將
StatusBar
和NavagationBar
設置為透明
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
- 獲得根布局mContentRoot宪肖,遍歷將mContentRoot的子View的FitsSystemWindows設置為false,參考ViewGroup的隱藏方法
makeOptionalFitsSystemWindows
final int count = mContentRoot.getChildCount();
for (int i = 0; i < count; i++) {
mContentRoot.getChildAt(i).setFitsSystemWindows(false);
}
- 給mContentRoot的上內邊距置為0健爬,否則圖片頂不到
StatusBar
mContentRoot.setPadding(mContentRoot.getPaddingLeft(), 0,
mContentRoot.getPaddingRight(), mContentRoot.getPaddingBottom());
- 讓設置了背景圖片的View的上內邊距加上
StatusBar
的高度控乾,否則內容會和狀態(tài)欄重合
drawableView.setPadding(drawableView.getPaddingLeft(), drawableView.getPaddingTop() + getStatusBarHeight(),
drawableView.getPaddingRight(), drawableView.getPaddingBottom());
最后的效果:
版本 | 對比 |
---|---|
4.4 | |
5.0 |
細心的已經發(fā)現了5.0的版本的狀態(tài)欄和4.4的有些不一樣,5.0的狀態(tài)欄是半透明的娜遵,為什么會這樣呢蜕衡?在代碼中針對4.4和5.0同樣設置的狀態(tài)欄透明標記,顯示的效果卻不同设拟。讓我們來看看源碼:
5.0如何實現全屏圖片
我們知道FLAG_TRANSLUCENT_STATUS
是在Window
類中通過addFlags
添加的慨仿,而它的使用則是在DecorView
中,DecorView
中通過calculateStatusBarColor
方法計算狀態(tài)欄的顏色
calculateStatusBarColor是如何計算狀態(tài)欄的顏色的
(flags & FLAG_TRANSLUCENT_STATUS) != 0 ? semiTransparentStatusBarColor
: (flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 ? statusBarColor
: Color.BLACK;
- 首先它會判斷是否有
FLAG_TRANSLUCENT_STATUS
標記纳胧,如果有這個標記镰吆,則會返回semiTransparentStatusBarColor
,這個顏色值是個常量mSemiTransparentStatusBarColor
跑慕,這個常量是在創(chuàng)建DecorView的時候賦值為R.color.system_bar_background_semi_transparent
万皿,可以看到在資源文件中的顏色值是百分之40透明度黑色的值,這就是為什么狀態(tài)欄在5.0顯示半透明的原因
<!-- Status bar color for semi transparent mode. -->
<color name="system_bar_background_semi_transparent">#66000000</color> <!-- 40% black -->
- 否則會判斷是否有
FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
標記核行,如果有這個標記牢硅,則會返回
statusBarColor,這個顏色是由PhoneWindow
的setStatusBarColor
方法決定的
修改狀態(tài)欄半透明的解決方案
可以從calculateStatusBarColor
方法的第二個判斷條件入手:
- 5.0以上移除
FLAG_TRANSLUCENT_STATUS
標記
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
- 添加
FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
標記
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
- 設置狀態(tài)欄顏色為透明色
getWindow().setStatusBarColor(Color.TRANSPARENT);
導航欄修改類似芝雪,最終代碼:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mContentRoot = (ViewGroup) findViewById(android.R.id.content);
makeOptionalFitsSystemWindows();
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
if (hasNavigationBar(this)) {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
}
int uiFlags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
uiFlags |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
uiFlags |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
uiFlags |= View.SYSTEM_UI_FLAG_VISIBLE;
getWindow().getDecorView().setSystemUiVisibility(
uiFlags | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
getWindow().setStatusBarColor(Color.TRANSPARENT);
getWindow().setNavigationBarColor(Color.TRANSPARENT);
}
mContentRoot.setPadding(mContentRoot.getPaddingLeft(), 0,
mContentRoot.getPaddingRight(), mContentRoot.getPaddingBottom());
drawableView.setPadding(drawableView.getPaddingLeft(), drawableView.getPaddingTop() + getStatusBarHeight(),
drawableView.getPaddingRight(), drawableView.getPaddingBottom());
}
運行后的效果:
總結
- 對于5.0以上主要通過
getWindow().setStatusBarColor(barColor);
設置狀態(tài)欄的顏色减余;4.4到5.0之間主要在狀態(tài)欄的位置添加一個FrameLayout作為假狀態(tài)欄,通過改變這個FrameLayout的背景顏色來改變狀態(tài)欄的顏色惩系,注意的是狀態(tài)欄必須設置為透明 - 對于圖片沉浸式位岔,主要是通過設置狀態(tài)欄透明如筛,并將布局的FitsSystemWindows設置為false,讓statusBar和整個組件都在屏幕內赃承,就可以控制根布局的內邊距來實現
- FitsSystemWindows屬性的作用妙黍,設置為true,則組件都在屏幕內瞧剖,但是不包括statusBar拭嫁;設置成false后,整個屏幕都可以放置組件抓于,沒有statusBar和window之分
參考
與Status Bar和Navigation Bar相關的一些東西
全屏做粤、沉浸式、fitSystemWindow使用及原理分析:全方位控制“沉浸式”的實現