只需兩個函式終結狀態列疑難雜症

blankj發表於2017-08-02

Foreword

首先推廣下AndroidUtilCode,相信很多老司機們都見識過了,昨天完結了StatusBar的功能,釋出了最新1.8.0版本,StatusBar相關原始碼存在於BarUtils,推薦insight.io外掛來檢視原始碼更絲滑哦,想知根知底的話那就閱讀原始碼吧,後面我也會相應介紹。

該類是借鑑於StatusBarUtil,通過我對其原始碼的進一步分析,我想到了更為完美地來解決狀態列的疑難雜症,也就是所謂地站在巨人肩膀上,但其中間歷經的坎坷...欲語淚先流啊,好了,下面就讓我去前面探探路吧。

Functions

該工具類支援安卓SDK19及以上,我所設計的設定狀態列主要包括兩類,其一是設定狀態列顏色,其二是設定狀態列透明度,這兩大類在應用中主要包含六點。

  1. 設定狀態列顏色
  2. 設定佈局背景的狀態列透明度
  3. 設定頂部View的狀態列透明度
  4. 設定ViewPager中Fragment的狀態列
  5. 設定滑動返回的狀態列
  6. 設定DrawLayout的狀態列

下面我依據這六點來演示,效果圖的話附上API19和API25的。

設定狀態列顏色

啥也不說了,先上效果圖。

color19.gif
color19.gif

color25.gif
color25.gif

這是相關的Demo類,可以看到,我在其中只呼叫瞭如下兩行程式碼即可實現其效果。

private void updateStatusBar() {
    BarUtils.setStatusBarColor(this, mColor, mAlpha);
    BarUtils.addMarginTopEqualStatusBarHeight(mTvStatusAlpha);// 其實這個只需要呼叫一次即可
}複製程式碼

具體效果函式名已經表達得已經很清楚了,後面我會對其原理分析一遍,這裡只看相關效果及主要程式碼。

設定佈局背景的狀態列透明度

bg19.gif
bg19.gif

bg25.gif
bg25.gif

這是相關的Demo類,其主要程式碼如下所示,還是兩個函式解決。

private void updateStatusBar() {
    BarUtils.setStatusBarAlpha(BarStatusAlphaActivity.this, mAlpha);
    BarUtils.addMarginTopEqualStatusBarHeight(mTvStatusAlpha);// 其實這個只需要呼叫一次即可
}複製程式碼

設定頂部View的狀態列透明度

image19.gif
image19.gif

image25.gif
image25.gif

相關Demo類,由於沒有View需要新增MargionTop,所以只需一行程式碼即可解決。

private void updateStatusBar() {
    BarUtils.setStatusBarAlpha(BarStatusImageViewActivity.this, mAlpha, true);
}複製程式碼

設定ViewPager中Fragment的狀態列

fragment19.gif
fragment19.gif

fragment25.gif
fragment25.gif

這個比較特殊,因為ViewPager會預載入後面的Fragment,所以每一個Fragment都需要持有自己的StatusBar,這裡我們設定假狀態列即可,根據我後面的分析,你會發現我實現的狀態列都是假的,我們在每個需要狀態列的Fragment中新增如下View到頂層最上方即可。

<View
    android:id="@+id/fake_status_bar"
    android:layout_width="match_parent"
    android:layout_height="0dp" />複製程式碼

三個Demo類如下所示,[ColorDemo類]insight.io/github.com/…)

相關核心程式碼如下所示,由於沒有需要偏移MarginTop的,一行即可解決。

public void updateFakeStatusBar() {
    BarUtils.setStatusBarColor(fakeStatusBar, mColor, mAlpha);
}

public void updateFakeStatusBar() {
    BarUtils.setStatusBarAlpha(fakeStatusBar, mAlpha);
}

public void updateFakeStatusBar() {
    BarUtils.setStatusBarAlpha(fakeStatusBar, mAlpha);
}複製程式碼

設定滑動返回的狀態列

back19.gif
back19.gif

back25.gif
back25.gif

這是相關Demo類,由於頂部CheckBox需要偏移,其重點程式碼只需兩行即可。

private void updateStatusBar() {
    if (cbAlpha.isChecked()) {
        BarUtils.setStatusBarAlpha(this, mAlpha);
    } else {
        BarUtils.setStatusBarColor(this, mColor, mAlpha);
    }
    BarUtils.addMarginTopEqualStatusBarHeight(cbAlpha);// 其實這個只需要呼叫一次即可
}複製程式碼

由於滑動返回在API19中會出現桌面而不是之前的Activity介面,但這並不是我們需要關注的問題,我們需要關注的狀態列是否也在滑動即可,如果要求狀態列固定不變,不跟隨滑動的話,我們也可以做到,只需在呼叫函式後面加個引數true即可,也就是如下所示。

private void updateStatusBar() {
    if (cbAlpha.isChecked()) {
        BarUtils.setStatusBarAlpha(this, mAlpha, true);
    } else {
        BarUtils.setStatusBarColor(this, mColor, mAlpha, true);
    }
    BarUtils.addMarginTopEqualStatusBarHeight(cbAlpha);// 其實這個只需要呼叫一次即可
}複製程式碼

最後一個引數就是繪製的狀態列是否在DecorView中,false的話就是在ContentView中,後面會具體講解,下面我們來看最後一種情況。

設定DrawLayout的狀態列

drawer19.gif
drawer19.gif

drawer25.gif
drawer25.gif

這是相關Demo類,由於需要頂部CheckBox新增MarginTop,所以兩行程式碼即可解決。

private void updateStatusBar() {
    if (cbAlpha.isChecked()) {
        BarUtils.setStatusBarAlpha4Drawer(BarStatusDrawerActivity.this, rootLayout, fakeStatusBar, mAlpha, cbFront.isChecked());
    } else {
        BarUtils.setStatusBarColor4Drawer(BarStatusDrawerActivity.this, rootLayout, fakeStatusBar, mColor, mAlpha, cbFront.isChecked());
    }
    BarUtils.addMarginTopEqualStatusBarHeight(cbAlpha);// 其實這個只需要呼叫一次即可
}複製程式碼

需要注意的是,DrawerLayout需要新增android:fitsSystemWindows="true"這個屬性,另外就是和Fragment一樣,需要自己在頂層最上方新增假的狀態列。

好了,Demo已全部展示完畢,各位司機們應該都已經瞭解得差不多了,是不是如標題所說,每種效果只需兩個函式即可終結狀態列疑難雜症,如果還想要深入瞭解原始碼的話,那就繼續往下看吧,不需要了解的話那就跳過下面這節的講解,直接到最後面看我的結語吧。

How to achieve

首先我們看一張安卓UI架構圖,如下所示。

ui_frame.png
ui_frame.png

我設計的新增顏色狀態列或者透明狀態列分為兩種,一種是新增在DecorView中,另一種是新增在ContentView中,我在相應函式過載的最後一個引數boolean isDecor,就是控制是否新增到DecorView中,預設是false,也就是新增在ContentView中的,這也是滑動返回需要的效果,即狀態列可隨著滑動而偏移。

以上是其核心思想,之後的操作就是為以上服務,首先我們需要把狀態列設為透明狀態,這樣我們才可以自己繪製上我們自己的狀態列,也就是如下程式碼。

private static void transparentStatusBar(final 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) {
        int option = 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);
    }
}複製程式碼

這段程式碼即可實現透明狀態列,實現了這一步之後就是新增我們自己假狀態列了,首先是顏色狀態列,如下所示,

private static void addStatusBarColor(final Activity activity, final int color, final int alpha, boolean isDecor) {
    ViewGroup parent = isDecor ?
            (ViewGroup) activity.getWindow().getDecorView() :
            (ViewGroup) activity.findViewById(android.R.id.content);
    View fakeStatusBarView = parent.findViewWithTag(TAG_COLOR);
    if (fakeStatusBarView != null) {
        if (fakeStatusBarView.getVisibility() == View.GONE) {
            fakeStatusBarView.setVisibility(View.VISIBLE);
        }
        fakeStatusBarView.setBackgroundColor(getStatusBarColor(color, alpha));
    } else {
        parent.addView(createColorStatusBarView(parent.getContext(), color, alpha));
    }
}複製程式碼

程式碼很簡單,根據isDecor決定新增到哪個佈局,後面就是新增View的操作了。

有小夥伴對顏色狀態列的alpha肯定有疑問,說這alpha不對,並不是用來控制透明度的,的確,這個alpha並不是用來控制透明度的,這個alpha是材料設計中對狀態列陰影設定,預設效果值為112,下面是透明狀態列

private static void addStatusBarAlpha(final Activity activity, final int alpha, boolean isDecor) {
    ViewGroup parent = isDecor ?
            (ViewGroup) activity.getWindow().getDecorView() :
            (ViewGroup) activity.findViewById(android.R.id.content);
    View fakeStatusBarView = parent.findViewWithTag(TAG_ALPHA);
    if (fakeStatusBarView != null) {
        if (fakeStatusBarView.getVisibility() == View.GONE) {
            fakeStatusBarView.setVisibility(View.VISIBLE);
        }
        fakeStatusBarView.setBackgroundColor(Color.argb(alpha, 0, 0, 0));
    } else {
        parent.addView(createAlphaStatusBarView(parent.getContext(), alpha));
    }
}複製程式碼

和顏色狀態列很相似,我就不細說了,這個alpha,就是我們平時的透明度了。

由於我們實現的透明狀態列,這樣在佈局中會佔滿整個螢幕,所以我這裡提供了另外兩個函式,分別是addMarginTopEqualStatusBarHeightsubtractMarginTopEqualStatusBarHeight,也就是增加和減少View的MarginTop為狀態列高度,程式碼如下所示。

/**
 * 為view增加MarginTop為狀態列高度
 *
 * @param view view
 */
public static void addMarginTopEqualStatusBarHeight(@NonNull View view) {
    Object haveSetOffset = view.getTag(TAG_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(TAG_OFFSET, true);
}

/**
 * 為view減少MarginTop為狀態列高度
 *
 * @param view view
 */
public static void subtractMarginTopEqualStatusBarHeight(@NonNull View view) {
    Object haveSetOffset = view.getTag(TAG_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(TAG_OFFSET, false);
}複製程式碼

這樣就基本滿足了我們大部分情況了,但人生總有意外,比如ViewPager的預載入,這樣我們就需要新的方式來適應這種情況,也就是我們自己新增假的狀態列到佈局檔案中,然後呼叫函式即可,這種方法其實就是以上兩種方法的擴充,以上兩種是我幫你們加了假的在DecorView或者ContentView中,這種就是老司機們自己加到佈局中,所以其本質都是一樣,所以出來的效果也是一樣的,也就完美相容了。

總有刁民想害朕,這不,DrawerLayout就是那個異端,為他我單獨設計了兩個函式來針對它,在Demo中我也講解了其使用方式。設計過程中需要注意的是DrawerLayout的直屬子View需要設定setFitsSystemWindows(false);,具體細節可以參看原始碼。

以上基本就是設計的核心所在,老司機們想要實現什麼樣的效果隨你們自己挑選。

具體StatusBar相關API如下所示。

getStatusBarHeight                   : 獲取狀態列高度(px)
addMarginTopEqualStatusBarHeight     : 為view增加MarginTop為狀態列高度
subtractMarginTopEqualStatusBarHeight: 為view減少MarginTop為狀態列高度
setStatusBarColor                    : 設定狀態列顏色
setStatusBarAlpha                    : 設定狀態列透明度
setStatusBarColor4Drawer             : 為DrawerLayout設定狀態列顏色
setStatusBarAlpha4Drawer             : 為DrawerLayout設定狀態列透明度複製程式碼

Conclusion

柯基已經為你們探完路了,實現狀態列的設計沒少費我功夫,想實現什麼效果,最終只需兩個函式即可藥到病除,我們程式設計師不就是需要這種越簡單越好的東西麼,同意的話就快砸上你們的喜歡吧。

簡書第一時間發文:只需兩個函式終結狀態列疑難雜症
歡迎關注我的簡書:Blankj的簡書

相關文章