Android透明狀態欄——你要的只是幾個方法

以前寫的項目基本沒有考慮過Android透明狀態欄的,最近項目有用到,便網上搜索一番。搜到的文章要麼是講解不全、存在錯誤,要麼就是使用一些庫,也不說原理,上來就是給你看效果圖,然後十幾個方法讓你眼花繚亂,看源碼就是越看越複雜。算了,自己寫個簡單能用的就好。所以,本文將通俗易懂地介紹Android透明狀態欄原理,以及幾種常見使用場景的處理方法。

下面是實現的效果:

在這裏插入圖片描述
以上效果支持狀態欄和標題欄(ToolBar或者自定義)背景一致且可修改(改變顏色、漸變色、不透明度看你自己怎麼改),標題欄位置不被狀態欄遮擋,6.0之上淺色狀態欄背景修改字體顏色爲深色。支持DrawerLayout使用、Fragment使用。雖然效果很多,其實原理都是一樣的。

本文會教你如何修改狀態欄的顏色、如何解決狀態欄和標題欄內容重合的問題。

順便說下,這裏的透明狀態欄指的是佈局內容延伸到狀態欄的效果,網上很多人也叫做沉浸式狀態欄,這裏不做辯駁。

原理

透明狀態欄的設置存在兼容性。Android 4.4(API 19)之前不支持透明狀態欄,而Android 5.0(API 21)之後版本對於透明狀態欄則有不同的處理方式。所以,我們要兼容的就是保證4.4之前的版本雖然不支持透明狀態欄,但是UI效果還是要協調,而4.4之後的版本跟根據不同的API版本儘量達成一致的顯示效果。

設置透明狀態欄,最核心的方法就是下面這一個方法:

 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進行設置即可實現透明狀態欄。這個方法調用後,頁面佈局內容就會延伸至狀態欄,狀態欄即爲透明色。

在這裏插入圖片描述
但是,這裏要注意下,其帶來了一個問題是頁面佈局會延伸至狀態欄,你佈局中的標題欄和狀態欄重合了,這顯示不是我們想要的效果。我們想要的是原有標題欄還是在原來標題欄的位置,只不過狀態欄的顏色和標題欄一致。接下來,我們將要解決的就是標題欄和狀態欄重合的問題。這個問題,也是你看到的所有第三方庫想要幫你解決的問題。第三方庫爲了滿足廣大開發者的需求,也是爲了適配各種情況,通常會增加N多種接口或者方式來處理這個問題,但隨之而來帶來的就是複雜性,不易理解。接口方法太多了,通常都要組合搭配調用才能生效,不看源碼,光看接口估計會很懵逼。

好勒,接下來,我們就介紹幾種方式來解決標題欄和狀態欄重合問題。

標題欄和狀態欄重合問題

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

幾種解決辦法:

  1. 使用系統提供的fitSystemWindows屬性
  2. 給標題控件動態增加paddingTop
  3. 在DecorView中動態創建一個狀態欄高度的View,然後更改View背景爲想要的顏色,然後設置標題欄marginTop爲狀態欄高度
  4. 在佈局中定義一個假的用來充當狀態欄的View,動態設置其高度或者可見性
    不同方式都有各自的優劣,建議根據不同的場景來靈活應用。

方式一:系統提供的fitSystemWindows屬性

fitsSystemWindows屬性:

  1. 在透明狀態欄或者全屏時候生效。(4.4之前添加了這個屬性也沒有任何效果的,滿足兼容性)
  2. 如果將某個View的該屬性設置爲true,則系統會爲該View添加一個paddingTop,值爲狀態欄的高度。
  3. 如果多個View同時設置該屬性爲true,則只有第一個會起作用。此爲一般情況。
  4. 默認系統只處理第一個fitsSystemWindows,但每個控件自己可以單獨處理fitsSystemWindows。

fitsSystemWindows屬性爲true的時候,在透明狀態欄模式下,4.4(含)之後的版本控件,系統會針對掃描到的該屬性設置爲true的第一個控件設置一個狀態欄高度的paddingTop(原有View的paddingTop將會失效)。

知道上面的原理後,我們可以根據需要來爲哪個控件設置fitsSystemWindows屬性爲true。

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

比如下面的在原來標題欄RelativeLayout佈局外面包裝一個LinearLayout,然後給LinearLayout添加一個fitsSystemWindows屬性,並且修改LinearLayout的背景爲標題欄背景色。

在這裏插入圖片描述

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

方式二:給標題欄控件動態增加paddingTop

4.4之前的話就不加了。可以用代碼來動態增加,或者適用的話也可以放到values文件中。代碼獲取狀態欄高度:

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

給標題欄控件動態增加高度的方法:

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());
        }
    }

如果不是固定高度的控件,那麼直接增加一個狀態欄高度的paddingTop,如果是寫死高度的控件,則需要同時給高度增加一個狀態欄高度。

在應用此方法的時候要注意,這個titleBar控件增加了paddingTop或者高度後,其內部佈局要非常協調,不要影響到美觀了。如果你的標題欄用RelativeLayout寫的,然後裏面文本居中那麼使用此方法後顯示效果會很醜陋,可以參考第一種方式在外面包裝一層。

除了代碼控制,還可以根據API版本不同配置不同像素高度的status_bar_offset,然後設置到佈局中。通常,狀態欄高度是24或者25dp。在4.4(API 19)之前不需要處理重疊問題所以設置status_bar_offset爲0dp,在values-v19中設置status_bar_offset爲25dp,然後根據佈局的不同動態增加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>

除了上面這種寫法,還有很多種寫法,發揮你的想象力!

根據版本不同處理不同的padding這種,個人還是比較推崇的,不用寫太多代碼,直接佈局就寫好了。當然可能是針對一些比較簡單的頁面處理哈。

方式三:動態創建狀態欄高度的View添加到DecorView中,然後設置標題欄的marginTop爲狀態欄高度

很多第三方庫都喜歡這種方式。個人不喜歡用,個人鍾愛第二種和第一種哈哈。
這種方式也有侷限性,比如對於DrawerLayout的情況,如果採用這種方式將statusBar添加到DecorView上,會導致側邊欄也會被狀態欄所覆蓋。

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);
    }

方式四 在佈局中定義假的充當狀態欄的View,然後動態設置其高度。

這種方式是爲了解決方式三不適用於DrawerLayout的情況,因爲DecorView中添加的View是蓋在整個佈局上的,所以會遮擋側邊欄。那麼將其移動到佈局中自己可控就好勒。

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

狀態欄背景顏色爲淺色導致狀態字體看不清的問題解決

狀態欄顏色默認爲白色,如果你的狀態欄背景爲淺色,那麼會導致字體看不清楚。Android 6.0之後提供了方法設置爲深色字體。

下面設置爲light模式,則是讓狀態欄字體顏色變深,反之變淺。

 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);
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章