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

背景

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

酷安客戶端

雖然只是簡單的改變,但相對于傳統(tǒ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)版本下顯示效果達到高度統(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,當深度值為 0 時,狀態(tài)欄和導(dǎo)航欄的顏色就是第一個參數(shù)傳入的顏色值,即為第二個方法中的情況;當深度值不為 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 方法進行設(shè)置就好了。然后 Android 4.4 稍微麻煩一點,首先必須要添加 FLAG_TRANSLUCENT_STATUS 這個 flag 來把狀態(tài)欄設(shè)置為透明,然后再在狀態(tài)欄上面添加一個 view 來保證狀態(tài)欄的顏色,然后再調(diào)用 navigationBarExist 方法來判斷當前手機是否存在導(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ù)的,相當于在布局中添加 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)航欄中當透明度為 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)一些問題,達不到想要的效果,這里針對 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è)為透明色,此時相當于上面的完全透明的狀態(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)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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