android透明狀態(tài)欄——你要的只是幾個方法

以前寫的項目基本沒有考慮過Android透明狀態(tài)欄的锯七,最近項目有用到链快,便網上搜索一番。搜到的文章要么是講解不全眉尸、存在錯誤域蜗,要么就是使用一些庫,也不說原理噪猾,上來就是給你看效果圖霉祸,然后十幾個方法讓你眼花繚亂,看源碼就是越看越復雜袱蜡。算了丝蹭,自己寫個簡單能用的就好。所以戒劫,本文將通俗易懂地介紹Android透明狀態(tài)欄原理半夷,以及幾種常見使用場景的處理方法。

下面是實現(xiàn)的效果:


透明狀態(tài)欄.gif

以上效果支持狀態(tài)欄和標題欄(ToolBar或者自定義)背景一致且可修改(改變顏色迅细、漸變色、不透明度看你自己怎么改)淘邻,標題欄位置不被狀態(tài)欄遮擋茵典,6.0之上淺色狀態(tài)欄背景修改字體顏色為深色。支持DrawerLayout使用宾舅、Fragment使用统阿。雖然效果很多,其實原理都是一樣的筹我。

本文會教你如何修改狀態(tài)欄的顏色扶平、如何解決狀態(tài)欄和標題欄內容重合的問題

順便說下蔬蕊,這里的透明狀態(tài)欄指的是布局內容延伸到狀態(tài)欄的效果结澄,網上很多人也叫做沉浸式狀態(tài)欄,這里不做辯駁岸夯。

原理

透明狀態(tài)欄的設置存在兼容性麻献。Android 4.4(API 19)之前不支持透明狀態(tài)欄,而Android 5.0(API 21)之后版本對于透明狀態(tài)欄則有不同的處理方式猜扮。所以勉吻,我們要兼容的就是保證4.4之前的版本雖然不支持透明狀態(tài)欄,但是UI效果還是要協(xié)調旅赢,而4.4之后的版本跟根據(jù)不同的API版本盡量達成一致的顯示效果齿桃。

設置透明狀態(tài)欄惑惶,最核心的方法就是下面這一個方法:

 public static void makeStatusBarTransparent(Activity activity) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
            return;
        }
        Window window = activity.getWindow();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
            int option = window.getDecorView().getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
            window.getDecorView().setSystemUiVisibility(option);
            window.setStatusBarColor(Color.TRANSPARENT);
        } else {
            window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        }
    }

你應該在網上找到了很多版本,類似的代碼短纵。歸根到底集惋,就這一個方法對Activity進行設置即可實現(xiàn)透明狀態(tài)欄。這個方法調用后踩娘,頁面布局內容就會延伸至狀態(tài)欄刮刑,狀態(tài)欄即為透明色。


image.png

但是养渴,這里要注意下雷绢,其帶來了一個問題是頁面布局會延伸至狀態(tài)欄,你布局中的標題欄和狀態(tài)欄重合了理卑,這顯示不是我們想要的效果翘紊。我們想要的是原有標題欄還是在原來標題欄的位置,只不過狀態(tài)欄的顏色和標題欄一致藐唠。接下來帆疟,我們將要解決的就是標題欄和狀態(tài)欄重合的問題。這個問題宇立,也是你看到的所有第三方庫想要幫你解決的問題踪宠。第三方庫為了滿足廣大開發(fā)者的需求,也是為了適配各種情況妈嘹,通常會增加N多種接口或者方式來處理這個問題柳琢,但隨之而來帶來的就是復雜性,不易理解润脸。接口方法太多了柬脸,通常都要組合搭配調用才能生效,不看源碼毙驯,光看接口估計會很懵逼倒堕。

好勒,接下來爆价,我們就介紹幾種方式來解決標題欄和狀態(tài)欄重合問題垦巴。

標題欄和狀態(tài)欄重合問題

解決狀態(tài)欄和標題欄重疊的幾種方法原理都是一致的:讓布局中的標題欄有一個狀態(tài)欄高度的marginTop或者paddingTop即可。但因為4.4之前是不存在狀態(tài)欄重合這種問題允坚,所以也就不需要有一個狀態(tài)欄高度的marginTop或者paddingTop魂那,所以要考慮一個兼容性問題。

幾種解決辦法:

  • 使用系統(tǒng)提供的fitSystemWindows屬性
  • 給標題控件動態(tài)增加paddingTop
  • 在DecorView中動態(tài)創(chuàng)建一個狀態(tài)欄高度的View稠项,然后更改View背景為想要的顏色涯雅,然后設置標題欄marginTop為狀態(tài)欄高度
  • 在布局中定義一個假的用來充當狀態(tài)欄的View,動態(tài)設置其高度或者可見性

不同方式都有各自的優(yōu)劣展运,建議根據(jù)不同的場景來靈活應用活逆。

方式一:系統(tǒng)提供的fitSystemWindows屬性

fitsSystemWindows屬性:

  • 在透明狀態(tài)欄或者全屏時候生效精刷。(4.4之前添加了這個屬性也沒有任何效果的,滿足兼容性)
  • 如果將某個View的該屬性設置為true蔗候,則系統(tǒng)會為該View添加一個paddingTop怒允,值為狀態(tài)欄的高度。
  • 如果多個View同時設置該屬性為true锈遥,則只有第一個會起作用纫事。此為一般情況。
  • 默認系統(tǒng)只處理第一個fitsSystemWindows所灸,但每個控件自己可以單獨處理fitsSystemWindows丽惶。

fitsSystemWindows屬性為true的時候,在透明狀態(tài)欄模式下爬立,4.4(含)之后的版本控件钾唬,系統(tǒng)會針對掃描到的該屬性設置為true的第一個控件設置一個狀態(tài)欄高度的paddingTop(原有View的paddingTop將會失效)。

注意:上面這段話是我的個人理解侠驯,并不嚴謹抡秆。

知道上面的原理后,我們可以根據(jù)需要來為哪個控件設置fitsSystemWindows屬性為true吟策。

因為是設定paddingTop儒士,如果你的標題欄是一個固定高度比如50dp,那么設置后是不會有效的踊挠,畢竟高度寫死了的乍桂。所以呢通常設置這個屬性的View高度是一個wrap_content,如果不是的話呢效床,那么你就在外面包一個FrameLayout或者LinearLayout,然后將原來View的背景設置到外面的包裝Layout上权谁。

比如下面的在原來標題欄RelativeLayout布局外面包裝一個LinearLayout剩檀,然后給LinearLayout添加一個fitsSystemWindows屬性,并且修改LinearLayout的背景為標題欄背景色旺芽。


image.png

對了沪猴,我試過fitsSystemWindows如果在Fragment中使用貌似是不生效的。

方式二:給標題欄控件動態(tài)增加paddingTop

4.4之前的話就不加了采章∫吃澹可以用代碼來動態(tài)增加赢乓,或者適用的話也可以放到values文件中。代碼獲取狀態(tài)欄高度:

 public static int getStatusBarHeight() {
        Resources resources = Resources.getSystem();
        int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
        return resources.getDimensionPixelSize(resourceId);
    }

給標題欄控件動態(tài)增加高度的方法:

public static void fitTitleBar(Activity activity, final View titleBar) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
            return;
        }
        makeStatusBarTransparent(activity);
        final ViewGroup.LayoutParams layoutParams = titleBar.getLayoutParams();
        assert layoutParams != null;
        if (layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT ||
                layoutParams.height == ViewGroup.LayoutParams.MATCH_PARENT) {
            titleBar.post(new Runnable() {
                @Override
                public void run() {
                    layoutParams.height = titleBar.getHeight() + getStatusBarHeight();
                    titleBar.setPadding(titleBar.getPaddingLeft(),
                            titleBar.getPaddingTop() + getStatusBarHeight(),
                            titleBar.getPaddingRight(),
                            titleBar.getPaddingBottom());
                }
            });
        } else {
            layoutParams.height += getStatusBarHeight();
            titleBar.setPadding(titleBar.getPaddingLeft(),
                    titleBar.getPaddingTop() + getStatusBarHeight(),
                    titleBar.getPaddingRight(),
                    titleBar.getPaddingBottom());
        }
    }

如果不是固定高度的控件,那么直接增加一個狀態(tài)欄高度的paddingTop巴元,如果是寫死高度的控件,則需要同時給高度增加一個狀態(tài)欄高度。

在應用此方法的時候要注意,這個titleBar控件增加了paddingTop或者高度后岭参,其內部布局要非常協(xié)調,不要影響到美觀了尝艘。如果你的標題欄用RelativeLayout寫的演侯,然后里面文本居中那么使用此方法后顯示效果會很丑陋,可以參考第一種方式在外面包裝一層背亥。

除了代碼控制秒际,還可以根據(jù)API版本不同配置不同像素高度的status_bar_offset,然后設置到布局中狡汉。通常娄徊,狀態(tài)欄高度是24或者25dp。在4.4(API 19)之前不需要處理重疊問題所以設置status_bar_offset為0dp轴猎,在values-v19中設置status_bar_offset為25dp嵌莉,然后根據(jù)布局的不同動態(tài)增加padding。

比如:
values/dimens.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="status_bar_offset">0dp</dimen>
</resources>

values-v19/dimens.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="status_bar_offset">25dp</dimen>
</resources>

布局文件

<LinearLayout android:id="@+id/top_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_green_dark"
        android:paddingTop="@dimen/status_bar_offset"
        android:orientation="vertical">
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="50dp">
            <TextView android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Hello"
                android:textColor="@color/white"
                android:layout_centerInParent="true"/>
        </RelativeLayout>
    </LinearLayout>

除了上面這種寫法捻脖,還有很多種寫法锐峭,發(fā)揮你的想象力!

根據(jù)版本不同處理不同的padding這種可婶,個人還是比較推崇的沿癞,不用寫太多代碼,直接布局就寫好了矛渴。當然可能是針對一些比較簡單的頁面處理哈椎扬。

方式三:動態(tài)創(chuàng)建狀態(tài)欄高度的View添加到DecorView中,然后設置標題欄的marginTop為狀態(tài)欄高度

很多第三方庫都喜歡這種方式具温。個人不喜歡用蚕涤,個人鐘愛第二種和第一種哈哈。
這種方式也有局限性铣猩,比如對于DrawerLayout的情況揖铜,如果采用這種方式將statusBar添加到DecorView上,會導致側邊欄也會被狀態(tài)欄所覆蓋达皿。

public static View setStatusBarColor(Activity activity, int color) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
          return null;
    }
    makeStatusBarTransparent(activity);
    return applyStatusBarColor(activity, color);
}

private static View applyStatusBarColor(Activity activity, int color) {
    View fakeStatusBar = ensureStatusBar(activity);
    fakeStatusBar.setBackgroundColor(color);
    return fakeStatusBar;
}

private static View ensureStatusBar(Activity activity) {
    ViewGroup parent = (ViewGroup) activity.getWindow().getDecorView();
    View fakeStatusBar = parent.findViewWithTag(TAG_FAKE_STATUS_BAR);
    if (fakeStatusBar == null) {
        fakeStatusBar = createStatusBar(activity);
        fakeStatusBar.setTag(TAG_FAKE_STATUS_BAR);
        parent.addView(fakeStatusBar);
    }
    return fakeStatusBar;
}

private static View createStatusBar(Activity activity) {
    View statusBarView = new View(activity);
    ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight());
    statusBarView.setLayoutParams(layoutParams);
    return statusBarView;
}

public static void addMarginTopEqualStatusBarHeight(View view) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
          return;
    }
    view.setTag(TAG_OFFSET);
    Object haveSetOffset = view.getTag(KEY_OFFSET);
    if (haveSetOffset instanceof Boolean) {
        return;
    }
    ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
    layoutParams.setMargins(layoutParams.leftMargin,
              layoutParams.topMargin + getStatusBarHeight(),
              layoutParams.rightMargin,
              layoutParams.bottomMargin);
    view.setTag(KEY_OFFSET, true);
}


  public static void subtractMarginTopEqualStatusBarHeight(View view) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;
        Object haveSetOffset = view.getTag(KEY_OFFSET);
        if (haveSetOffset == null || !(Boolean) haveSetOffset) {
            return;
        }
        ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
        layoutParams.setMargins(layoutParams.leftMargin,
                layoutParams.topMargin - getStatusBarHeight(),
                layoutParams.rightMargin,
                layoutParams.bottomMargin);
        view.setTag(KEY_OFFSET, false);
    }

方式四 在布局中定義假的充當狀態(tài)欄的View天吓,然后動態(tài)設置其高度。

這種方式是為了解決方式三不適用于DrawerLayout的情況峦椰,因為DecorView中添加的View是蓋在整個布局上的龄寞,所以會遮擋側邊欄。那么將其移動到布局中自己可控就好勒汤功。

具體代碼不提供了物邑,有思路就好。

狀態(tài)欄背景顏色為淺色導致狀態(tài)字體看不清的問題解決

狀態(tài)欄顏色默認為白色,如果你的狀態(tài)欄背景為淺色拂封,那么會導致字體看不清楚茬射。Android 6.0之后提供了方法設置為深色字體。

下面設置為light模式冒签,則是讓狀態(tài)欄字體顏色變深在抛,反之變淺。

 public static void setStatusBarLightMode(Activity activity, boolean isLightMode) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            Window window = activity.getWindow();
            int option = window.getDecorView().getSystemUiVisibility();
            if (isLightMode) {
                option |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
            } else {
                option &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
            }
            window.getDecorView().setSystemUiVisibility(option);
        }
    }

綜述

綜上萧恕,提供參考代碼地址:https://github.com/luckyshane/TintBar刚梭。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市票唆,隨后出現(xiàn)的幾起案子朴读,更是在濱河造成了極大的恐慌,老刑警劉巖走趋,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件衅金,死亡現(xiàn)場離奇詭異,居然都是意外死亡簿煌,警方通過查閱死者的電腦和手機氮唯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來姨伟,“玉大人惩琉,你說我怎么就攤上這事《峄模” “怎么了瞒渠?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長技扼。 經常有香客問我伍玖,道長,這世上最難降的妖魔是什么剿吻? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任私沮,我火速辦了婚禮,結果婚禮上和橙,老公的妹妹穿的比我還像新娘。我一直安慰自己造垛,他們只是感情好魔招,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著五辽,像睡著了一般办斑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天乡翅,我揣著相機與錄音鳞疲,去河邊找鬼。 笑死蠕蚜,一個胖子當著我的面吹牛尚洽,可吹牛的內容都是我干的。 我是一名探鬼主播靶累,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼腺毫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了挣柬?” 一聲冷哼從身側響起潮酒,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎邪蛔,沒想到半個月后急黎,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡侧到,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年勃教,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片床牧。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡荣回,死狀恐怖,靈堂內的尸體忽然破棺而出戈咳,到底是詐尸還是另有隱情心软,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布著蛙,位于F島的核電站删铃,受9級特大地震影響,放射性物質發(fā)生泄漏踏堡。R本人自食惡果不足惜猎唁,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望顷蟆。 院中可真熱鬧诫隅,春花似錦、人聲如沸帐偎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽削樊。三九已至豁生,卻和暖如春兔毒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背甸箱。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工育叁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人芍殖。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓豪嗽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親围小。 傳聞我的和親對象是個殘疾皇子昵骤,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內容