透明狀態(tài)欄和導(dǎo)航欄的終極解決方案

背景

在我做 Android 開發(fā)之前戴已,我就發(fā)現(xiàn)有些 App 的狀態(tài)欄和導(dǎo)航欄有透明效果蹲缠,或者是沉浸式效果,比如說酷安的客戶端,是像這個樣子的

酷安客戶端

雖然只是簡單的改變契沫,但相對于傳統(tǒng)的上下兩個黑條來說采蚀,視覺效果會美觀很多,我當(dāng)時挺糾結(jié)很多主流應(yīng)用沒有這種效果,還特意安裝了一個 xposed 框架的模塊來強制實現(xiàn)沉浸式狀態(tài)欄和導(dǎo)航欄瓶摆,不過貌似那個模塊會影響性能,從那時我就決定性宏,如果將來我做 Android 開發(fā)群井,一定會讓我開發(fā)的應(yīng)用都使用這種效果,如今終于實現(xiàn)啦毫胜!

開源庫

經(jīng)過對大量應(yīng)用的觀察书斜,我發(fā)現(xiàn)這種透明狀態(tài)欄和導(dǎo)航欄或者叫沉浸式狀態(tài)欄和導(dǎo)航欄的效果主要有以下幾種:
1、自定義顏色的狀態(tài)欄和導(dǎo)航欄酵使;
2荐吉、半透明的狀態(tài)欄和導(dǎo)航欄;
3口渔、完全透明的狀態(tài)欄和導(dǎo)航欄(其實就是第二種的極限狀態(tài)样屠,我更喜歡 叫這種為沉浸式狀態(tài)欄和導(dǎo)航欄);
4缺脉、隱藏狀態(tài)欄和導(dǎo)航欄痪欲。

效果分別如下:

自定義顏色
半透明
完全透明
隱藏

事實上,在 github 上也有不少關(guān)于這方面的開源項目攻礼,不過這些開源項目大多只是針對狀態(tài)欄實現(xiàn)了透明或者沉浸式的效果业踢,而對下方的導(dǎo)航欄并沒有做相應(yīng)的處理,于是我自己寫了一個針對狀態(tài)欄和導(dǎo)航欄都實現(xiàn)透明或者沉浸式的效果的開源庫礁扮,地址如下:

UltimateBar

這里要特別說明一下知举,狀態(tài)欄和導(dǎo)航欄透明是在 Android 4.4 開始支持的,但是 Android 4.4 的實現(xiàn)原理和 Android 5.0 以上的實現(xiàn)原理并不一樣深员,這就導(dǎo)致如果在 Android 5.0 以上如果使用 Android 4.4 的實現(xiàn)方法會出現(xiàn)顯示效果不一致的問題负蠕,我寫的這個庫分別對 Android 4.4 和 Android5.0 以上做了處理,使它在不同的系統(tǒng)版本下顯示效果達(dá)到高度統(tǒng)一倦畅,使用這個庫遮糖,首先需要添加依賴:

compile 'org.zackratos:ultimatebar:1.0.3'

接下來對上面四種情況分別作介紹。

自定義顏色的狀態(tài)欄和導(dǎo)航欄

要設(shè)置自定義顏色的狀態(tài)欄和導(dǎo)航欄只需要在 onCreate 方法中調(diào)用如下代碼:

UltimateBar ultimateBar = new UltimateBar(this);
ultimateBar.setColorBar(ContextCompat.getColor(this, R.color.DeepSkyBlue));

那么他的內(nèi)部是怎么實現(xiàn)的呢叠赐,查看源碼可以發(fā)現(xiàn)欲账,內(nèi)部源碼是這樣的:

@TargetApi(Build.VERSION_CODES.KITKAT)
public void setColorBar(@ColorInt int color, int alpha) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        Window window = activity.getWindow();
        window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
        window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
        int alphaColor = alpha == 0 ? color : calculateColor(color, alpha);
        window.setStatusBarColor(alphaColor);
        window.setNavigationBarColor(alphaColor);
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        Window window = activity.getWindow();
        window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        int alphaColor = alpha == 0 ? color : calculateColor(color, alpha);
        ViewGroup decorView = (ViewGroup) window.getDecorView();
        decorView.addView(createStatusBarView(activity, alphaColor));
        if (navigationBarExist(activity)) {
            decorView.addView(createNavBarView(activity, alphaColor));
            window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
        }
        setRootView(activity, true);
    }
}


@TargetApi(Build.VERSION_CODES.KITKAT)
public void setColorBar(@ColorInt int color) {
    setColorBar(color, 0);
}

我們可以看到第一個方法里面?zhèn)魅肓藘蓚€參數(shù),第一個參數(shù)是自定義的顏色值芭概,第二個參數(shù)是顏色深度值赛不,最小為 0,最大為 255罢洲,當(dāng)深度值為 0 時踢故,狀態(tài)欄和導(dǎo)航欄的顏色就是第一個參數(shù)傳入的顏色值文黎,即為第二個方法中的情況;當(dāng)深度值不為 0 時殿较,會根據(jù)深度值計算得到最終的顏色值耸峭,然后設(shè)置到狀態(tài)欄和導(dǎo)航欄上面。

正如前面所說淋纲,這里分別針對 Android 4.4 和 Android 5.0 以上做了不同處理劳闹,首先來看 Android 5.0 以上的情況,事實上 Android 5.0 以上的實現(xiàn)非常簡單洽瞬,因為 Android 5.0 以上可以直接設(shè)置狀態(tài)欄和導(dǎo)航欄的顏色本涕,所以只需要先得到最終的顏色值,然后調(diào)用 setStatusBarColor 和 setNavigationBarColor 方法進(jìn)行設(shè)置就好了伙窃。然后 Android 4.4 稍微麻煩一點菩颖,首先必須要添加 FLAG_TRANSLUCENT_STATUS 這個 flag 來把狀態(tài)欄設(shè)置為透明,然后再在狀態(tài)欄上面添加一個 view 來保證狀態(tài)欄的顏色对供,然后再調(diào)用 navigationBarExist 方法來判斷當(dāng)前手機是否存在導(dǎo)航欄位他,如果存在,對導(dǎo)航欄做同樣的處理产场,最后必須調(diào)用 setRootView 方法,這個方法是干嘛的呢舞竿,看一下它的代碼:

private void setRootView(Activity activity, boolean fit) {
    ViewGroup parent = (ViewGroup) activity.findViewById(android.R.id.content);
    for (int i = 0, count = parent.getChildCount(); i < count; i++) {
        View childView = parent.getChildAt(i);
        if (childView instanceof ViewGroup) {
            childView.setFitsSystemWindows(fit);
            ((ViewGroup)childView).setClipToPadding(fit);
        }
    }
}

可以看到京景,這個方法是用來設(shè)置布局的子 view 的 fitsSystemWindows 參數(shù)的,相當(dāng)于在布局中添加 android:fitsSystemWindows="true"骗奖,如果不調(diào)用這個方法确徙,就會導(dǎo)致布局中的內(nèi)容覆蓋到狀態(tài)欄和導(dǎo)航欄上面了。

半透明的狀態(tài)欄和導(dǎo)航欄

半透明狀態(tài)欄和導(dǎo)航欄的使用方法也非常簡單执桌,只要在 onCreate 方法中調(diào)用以下代碼:

UltimateBar ultimateBar = new UltimateBar(this);
ultimateBar.setTransparentBar(Color.BLUE, 50);

同樣的看一下它的內(nèi)部實現(xiàn)鄙皇,如下:

@TargetApi(Build.VERSION_CODES.KITKAT)
public void setTransparentBar(@ColorInt int color, int alpha) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        Window window = activity.getWindow();
        View decorView = window.getDecorView();
        int option = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
        decorView.setSystemUiVisibility(option);

        int finalColor = alpha == 0 ? Color.TRANSPARENT :
                Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color));

        window.setNavigationBarColor(finalColor);
        window.setStatusBarColor(finalColor);

    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
        Window window = activity.getWindow();
        window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        ViewGroup decorView = (ViewGroup) window.getDecorView();
        int finalColor = alpha == 0 ? Color.TRANSPARENT :
                Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color));
        decorView.addView(createStatusBarView(activity, finalColor));
        if (navigationBarExist(activity)) {
            window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
            decorView.addView(createNavBarView(activity, finalColor));
        }
    }

}

兩個參數(shù)分別表示顏色和透明度,透明度最小為 0仰挣,最大為 255伴逸,對于 Android 5.0 及以上,需要先添加 SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION膘壶,SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN错蝴,SYSTEM_UI_FLAG_LAYOUT_STABLE 三個 flag,以保證布局的內(nèi)容可以覆蓋到狀態(tài)欄和導(dǎo)航欄上面颓芭,然后同樣的調(diào)用 setStatusBarColor 和 setNavigationBarColor 方法來設(shè)置狀態(tài)欄和導(dǎo)航欄顏色顷锰,不過這里的顏色都是經(jīng)過計算的半透明的顏色,對于 Android 4.4亡问,跟之前的自定義顏色一樣官紫,首先需要添加 FLAG_TRANSLUCENT_STATUS 這個 flag 保證狀態(tài)欄透明,然后再在狀態(tài)欄上添加一個半透明的 view,然后調(diào)用 navigationBarExist 方法判斷導(dǎo)航欄是否存在束世,如果存在酝陈,也做相同的處理,這里要注意良狈,因為半透明狀態(tài)欄和導(dǎo)航欄需要布局內(nèi)容覆蓋到狀態(tài)欄和導(dǎo)航欄上面的效果后添,所以在這里不能調(diào)用 setRootView 方法。

完全透明的狀態(tài)欄和導(dǎo)航欄

其實完全透明的狀態(tài)欄和導(dǎo)航欄就是半透明的狀態(tài)欄和導(dǎo)航欄中當(dāng)透明度為 0 的情況薪丁,只需在 onCreate 方法中調(diào)用如下方法:

UltimateBar ultimateBar = new UltimateBar(this);
ultimateBar.setImmersionBar();

查看它的內(nèi)部實現(xiàn)可以發(fā)現(xiàn)它是這么調(diào)用的:

@TargetApi(Build.VERSION_CODES.KITKAT)
public void setImmersionBar() {
    setTransparentBar(Color.TRANSPARENT, 0);
}

就是半透明狀態(tài)欄和導(dǎo)航欄的特殊情況遇西,不做過多介紹了。

隱藏狀態(tài)欄和導(dǎo)航欄

這種情況比較常見了严嗜,一般玩游戲粱檀,看視頻就是這種效果,這種效果的實現(xiàn)有點特殊漫玄,必須重寫 Activity 的 onWindowFocusChanged 方法茄蚯,如下:

@Override
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    if (hasFocus) {
        UltimateBar ultimateBar = new UltimateBar(this);
        ultimateBar.setHintBar();
    }
}

它的內(nèi)部實現(xiàn)也比較簡單,如下:

@TargetApi(Build.VERSION_CODES.KITKAT)
public void setHintBar() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        View decorView = activity.getWindow().getDecorView();
        decorView.setSystemUiVisibility(
                View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                        | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                        | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                        | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                        | View.SYSTEM_UI_FLAG_FULLSCREEN
                        | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
    }
}

就是添加幾個 flag睦优,這是固定寫法渗常,也不做過多介紹了。

針對 DrawerLayout 的實現(xiàn)

還有一種特殊情況汗盘,就是對于 DrawerLayout皱碘,上面的方法會出現(xiàn)一些問題,達(dá)不到想要的效果隐孽,這里針對 DrawerLayout 做了特殊處理癌椿,一般來說,對于 DrawerLayout 只要實現(xiàn)自定義顏色的狀態(tài)欄和導(dǎo)航欄效果就好了菱阵,其他情況就不用考慮了踢俄,可以在 onCrate 調(diào)用如下代碼:

UltimateBar ultimateBar = new UltimateBar(this);
ultimateBar.setColorBarForDrawer(ContextCompat.getColor(this, R.color.DeepSkyBlue));

但是這樣其實還是不夠的,還必須要在布局文件中在 DawerLayout 的子 view 的主界面添加 android:fitsSystemWindows="true"晴及,就像這樣:

<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true"
        android:orientation="vertical">
    </LinearLayout>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/SpringGreen"
        android:layout_gravity="left"/>

</android.support.v4.widget.DrawerLayout>

注意都办,這里是在 DawerLayout 下面的主界面添加,DawerLayout 本身以及它下面的抽屜都不能添加抗俄,原因后面會說明脆丁,然后同樣看一下內(nèi)部實現(xiàn):

@TargetApi(Build.VERSION_CODES.KITKAT)
public void setColorBarForDrawer(@ColorInt int color, int alpha) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        Window window = activity.getWindow();
        ViewGroup decorView = (ViewGroup) window.getDecorView();
        int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
        if (navigationBarExist(activity)) {
            option = option | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
        }
        decorView.setSystemUiVisibility(option);
        window.setNavigationBarColor(Color.TRANSPARENT);
        window.setStatusBarColor(Color.TRANSPARENT);
        int alphaColor = alpha == 0 ? color : calculateColor(color, alpha);
        decorView.addView(createStatusBarView(activity, alphaColor), 0);
        if (navigationBarExist(activity)) {
            decorView.addView(createNavBarView(activity, alphaColor), 1);
        }
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        Window window = activity.getWindow();
        window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        ViewGroup decorView = (ViewGroup) window.getDecorView();
        int alphaColor = alpha == 0 ? color : calculateColor(color, alpha);
        decorView.addView(createStatusBarView(activity, alphaColor), 0);
        if (navigationBarExist(activity)) {
            window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
            decorView.addView(createNavBarView(activity, alphaColor), 1);
        }
    }
}



@TargetApi(Build.VERSION_CODES.KITKAT)
public void setColorBarForDrawer(@ColorInt int color) {
    setColorBarForDrawer(color, 0);
}

這里稍微有點復(fù)雜,參數(shù)的傳遞和前面是一樣的动雹,就不多解釋了槽卫,對于 Android 5.0 以上的情況,首先添加前面兩個 flag 保證布局內(nèi)容能夠覆蓋到狀態(tài)欄上面胰蝠,然后判斷是否存在導(dǎo)航欄歼培,如果存在震蒋,再添加第三個 flag 保證布局內(nèi)容可以覆蓋到導(dǎo)航欄上面,然后狀態(tài)欄和導(dǎo)航欄都設(shè)為透明色躲庄,此時相當(dāng)于上面的完全透明的狀態(tài)欄和導(dǎo)航欄查剖,最后再分別在狀態(tài)欄和導(dǎo)航欄上面添加一個 view 保證狀態(tài)欄和導(dǎo)航欄有顏色,這樣就既保證了 DrawerLayout 可以覆蓋到狀態(tài)欄和導(dǎo)航欄上面噪窘,又保證了 DrawerLayout 下面的主布局內(nèi)容不會覆蓋到狀態(tài)欄和導(dǎo)航欄上面笋庄,最后的效果就是抽屜的內(nèi)容是覆蓋到狀態(tài)欄和導(dǎo)航欄上面的,而住布局的內(nèi)容不會覆蓋到狀態(tài)欄和導(dǎo)航欄的上面倔监,然后對于 Android 4.4直砂,其實和前面正常情況的設(shè)置自定義顏色的狀態(tài)欄和導(dǎo)航欄是一樣的,只是這里沒有調(diào)用 setRootView 方法浩习,而是在 DrawerLayout 下面的主布局中添加了 android:fitsSystemWindows="true"静暂,同樣實現(xiàn)了抽屜的內(nèi)容可以覆蓋到狀態(tài)欄和導(dǎo)航欄上面,而主布局的內(nèi)容不會覆蓋到狀態(tài)欄和導(dǎo)航欄上面谱秽,最后的效果如下圖

抽屜未抽出
抽屜抽出一半
抽屜完全抽出

大致內(nèi)容也就這么多了洽蛀,最后再把這個庫的地址貼一遍:

UltimateBar

如果覺得這個庫對你的開發(fā)有幫助,歡迎 star疟赊,歡迎 fork郊供,如果發(fā)現(xiàn)有什么問題或者有什么修改建議,歡迎反饋近哟,謝謝颂碘!

最后的最后,我最近在找工作椅挣,發(fā)現(xiàn)好多公司都太坑,有沒有哪位大俠可以幫我內(nèi)推一下塔拳,地點不限鼠证,我將不甚感激!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末靠抑,一起剝皮案震驚了整個濱河市量九,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌颂碧,老刑警劉巖荠列,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異载城,居然都是意外死亡肌似,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門诉瓦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來川队,“玉大人力细,你說我怎么就攤上這事」潭睿” “怎么了眠蚂?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長斗躏。 經(jīng)常有香客問我逝慧,道長,這世上最難降的妖魔是什么啄糙? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任笛臣,我火速辦了婚禮,結(jié)果婚禮上迈套,老公的妹妹穿的比我還像新娘捐祠。我一直安慰自己,他們只是感情好桑李,可當(dāng)我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布踱蛀。 她就那樣靜靜地躺著,像睡著了一般贵白。 火紅的嫁衣襯著肌膚如雪率拒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天禁荒,我揣著相機與錄音猬膨,去河邊找鬼。 笑死呛伴,一個胖子當(dāng)著我的面吹牛勃痴,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播热康,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼沛申,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了姐军?” 一聲冷哼從身側(cè)響起铁材,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎奕锌,沒想到半個月后著觉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡惊暴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年饼丘,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缴守。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡葬毫,死狀恐怖镇辉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情贴捡,我是刑警寧澤忽肛,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站烂斋,受9級特大地震影響屹逛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜汛骂,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一罕模、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧帘瞭,春花似錦淑掌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至媒殉,卻和暖如春担敌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背廷蓉。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工全封, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人桃犬。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓刹悴,卻偏偏與公主長得像,于是被迫代替她去往敵國和親攒暇。 傳聞我的和親對象是個殘疾皇子颂跨,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容