沉浸式設計以及兼容

什么是沉浸式

可以參考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屬性
colorPrimary_4.3.png
4.4 colorPrimaryDark屬性
colorPrimary_4.4.png
5.0 colorPrimaryDark屬性
colorPrimary_5.0.png

那么如何兼容4.4呢?

通過android:windowTranslucentStatus屬性兼容4.4

v19\styles.xml中給主題添加以下屬性

<item name="android:windowTranslucentStatus">true</item>
版本 說明 對比
4.3 android:windowTranslucentStatus屬性
windowTranslucentStatus_4.3.png
4.4 android:windowTranslucentStatus屬性
windowTranslucentStatus_4.4.png
5.0 android:windowTranslucentStatus屬性
windowTranslucentStatus_5.0.png

如果要用此方法兼容4.4版本勤众,則許創(chuàng)建v21\styles.xml,給5.0以后的版本提供以下主題:

<item name="colorPrimaryDark">@color/red</item>

否則匾乓,5.0以上會沿用v19\styles.xml中定義的主題屬性欢唾,效果如下圖

bad_5.0.png

如果讓主題去掉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
noActionBar_4.3.png
4.4 NoActionBar
noActionBar_4.4.png
5.0 NoActionBar
noActionBar_5.0.png

從結果可以看出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后,整個屏幕都可以放置組件虱疏,沒有statusBarwindow之分

這樣,如果在布局中給根布局加上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系統上會顯示成下面這樣:

fitsSystemWindows_4.4.png

并沒有達到我們想要的效果做瞪,雖然能和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">
final_4.4.png

這樣子雖然能達到沉浸式效果乳蛾,但是背景顏色卻固定了暗赶,而且每個界面的根布局都得添加fitsSystemWindow屬性很麻煩鄙币,如何統一有效得兼容4.4以上的系統來達到沉浸式效果呢?

最終兼容方案

針對5.0以上的系統

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            getWindow().setStatusBarColor(barColor);
} 

針對4.4到5.0之間的系統

  1. StatusBar設置為透明
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
  1. 獲取到根布局mContentRoot蹂随,設置mContentRoot的FitsSystemWindows
mContentRoot = findViewById(android.R.id.content);
mContentRoot.setFitsSystemWindows(true);
  1. 創(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下激率,

  1. 可以通過反射拿到想要的資源的值
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;
}
  1. 可以通過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之間的系統

  1. NavagationBar設置為透明
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
  1. 判斷是否存在導航欄咳燕,有些手機沒有導航欄,通過物理屏幕減去內容屏幕的相應寬高判斷
    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;
    }
  1. 如果存在導航欄招盲,則在導航欄對應的位置上創(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);

最終效果如圖:

final_navigation_4.4.png

擴展 —— 如何實現圖片全屏沉浸式效果

結合之前分析的如何實現顏色沉浸是效果聪蘸,得出:

  1. StatusBarNavagationBar設置為透明
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
  1. 獲得根布局mContentRoot宪肖,遍歷將mContentRoot的子View的FitsSystemWindows設置為false,參考ViewGroup的隱藏方法makeOptionalFitsSystemWindows
final int count = mContentRoot.getChildCount();
for (int i = 0; i < count; i++) {
    mContentRoot.getChildAt(i).setFitsSystemWindows(false);
}
  1. 給mContentRoot的上內邊距置為0健爬,否則圖片頂不到StatusBar
mContentRoot.setPadding(mContentRoot.getPaddingLeft(), 0,
                    mContentRoot.getPaddingRight(), mContentRoot.getPaddingBottom());
  1. 讓設置了背景圖片的View的上內邊距加上StatusBar的高度控乾,否則內容會和狀態(tài)欄重合
drawableView.setPadding(drawableView.getPaddingLeft(), drawableView.getPaddingTop() + getStatusBarHeight(),
                    drawableView.getPaddingRight(), drawableView.getPaddingBottom());

最后的效果:

版本 對比
4.4
pic_immrison_4.4.png
5.0
pic_immrison_5.0.png

細心的已經發(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;
  1. 首先它會判斷是否有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 -->
  1. 否則會判斷是否有FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS標記核行,如果有這個標記牢硅,則會返回
    statusBarColor,這個顏色是由PhoneWindowsetStatusBarColor方法決定的

修改狀態(tài)欄半透明的解決方案

可以從calculateStatusBarColor方法的第二個判斷條件入手:

  1. 5.0以上移除FLAG_TRANSLUCENT_STATUS標記
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
  1. 添加FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS標記
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
  1. 設置狀態(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());
        }

運行后的效果:


final_5.0.png

總結

  1. 對于5.0以上主要通過getWindow().setStatusBarColor(barColor);設置狀態(tài)欄的顏色减余;4.4到5.0之間主要在狀態(tài)欄的位置添加一個FrameLayout作為假狀態(tài)欄,通過改變這個FrameLayout的背景顏色來改變狀態(tài)欄的顏色惩系,注意的是狀態(tài)欄必須設置為透明
  2. 對于圖片沉浸式位岔,主要是通過設置狀態(tài)欄透明如筛,并將布局的FitsSystemWindows設置為false,讓statusBar和整個組件都在屏幕內赃承,就可以控制根布局的內邊距來實現
  3. FitsSystemWindows屬性的作用妙黍,設置為true,則組件都在屏幕內瞧剖,但是不包括statusBar拭嫁;設置成false后,整個屏幕都可以放置組件抓于,沒有statusBar和window之分

參考

與Status Bar和Navigation Bar相關的一些東西
全屏做粤、沉浸式、fitSystemWindow使用及原理分析:全方位控制“沉浸式”的實現

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末捉撮,一起剝皮案震驚了整個濱河市怕品,隨后出現的幾起案子,更是在濱河造成了極大的恐慌巾遭,老刑警劉巖肉康,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異灼舍,居然都是意外死亡吼和,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進店門骑素,熙熙樓的掌柜王于貴愁眉苦臉地迎上來炫乓,“玉大人,你說我怎么就攤上這事献丑∧┑罚” “怎么了?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵创橄,是天一觀的道長箩做。 經常有香客問我,道長妥畏,這世上最難降的妖魔是什么邦邦? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮咖熟,結果婚禮上,老公的妹妹穿的比我還像新娘柳畔。我一直安慰自己馍管,他們只是感情好,可當我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布薪韩。 她就那樣靜靜地躺著确沸,像睡著了一般捌锭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上罗捎,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天观谦,我揣著相機與錄音,去河邊找鬼桨菜。 笑死豁状,一個胖子當著我的面吹牛,可吹牛的內容都是我干的倒得。 我是一名探鬼主播泻红,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼霞掺!你這毒婦竟也來了谊路?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤菩彬,失蹤者是張志新(化名)和其女友劉穎缠劝,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體骗灶,經...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡惨恭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了矿卑。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片喉恋。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖母廷,靈堂內的尸體忽然破棺而出轻黑,到底是詐尸還是另有隱情,我是刑警寧澤琴昆,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布氓鄙,位于F島的核電站,受9級特大地震影響业舍,放射性物質發(fā)生泄漏抖拦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一舷暮、第九天 我趴在偏房一處隱蔽的房頂上張望态罪。 院中可真熱鬧,春花似錦下面、人聲如沸复颈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽耗啦。三九已至凿菩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間帜讲,已是汗流浹背衅谷。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留似将,地道東北人获黔。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像玩郊,于是被迫代替她去往敵國和親肢执。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,514評論 2 348

推薦閱讀更多精彩內容