1. 程式人生 > >管理狀態列和導航欄

管理狀態列和導航欄

系統欄(system bar)是用來顯示通知、裝置狀態和導航的螢幕區域。一般來說,系統欄(system bar)包括狀態列(status bar)和導航欄(navigation bar),它們與應用程式同時顯示在螢幕上。而相簿、視訊等沉浸式的應用可以淡化系統欄來創造一個更加專注的體驗環境,甚至是完全隱藏系統欄。

下面來介紹如何在不同的版本中淡化和隱藏系統欄,營造一個沉浸式的使用者體驗,同時還可以快速作業系統欄。

注:本文參考Android Develop Training - Manager the System UI。

一、淡化系統欄(System Bar)

由於Android的早期版本沒有提供API來淡化系統欄,下面只介紹如何在Android 4.0及更高的版本中淡化系統欄。

(1).淡化狀態列和導航欄
狀態列和導航欄在被淡化後,依舊會佔據螢幕空間,只是其中的圖示和操作按鈕變成了灰白色的小圓點。
使用SYSTEM_UI_FLAG_LOW_PROFILE標識,可以達到淡化狀態列和導航欄的效果。程式碼如下。
// This example uses decor view, but you can use any visible view.
View decorView = getActivity().getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_LOW_PROFILE;
decorView.setSystemUiVisibility(uiOptions);

一旦使用者操作了狀態列和導航欄,這個標識就會被清除,使狀態列和導航欄重新顯現。標識被清除後,如果需要再次淡化狀態列和導航欄,必須重新設定這個標識。

     

左圖為預設狀態,右圖為淡化之後的狀態。

(2).顯示狀態列和導航欄
在狀態列和導航欄添加了SYSTEM_UI_FLAG_LOW_PROFILE標識被淡化後,如果需要再次顯示出來,可以使用如下方法清除標識。
View decorView = getActivity().getWindow().getDecorView();
// Calling setSystemUiVisibility() with a value of 0 clears
// all flags.
decorView.setSystemUiVisibility(0);

二、隱藏狀態列(Status Bar)

隱藏狀態列可以讓應用得到更多的展示空間,從而提供一個更加沉浸式的使用者體驗。下面介紹如何在不同的Android版本中隱藏狀態列。

左圖為顯示狀態列的效果,右圖為狀態列隱藏後的顯示效果。

     

(1).在4.0及以下版本中隱藏狀態列
在Android 4.0及以下版本中,可以通過設定WindowManager來隱藏狀態列。可以使用java程式碼動態設定,也可以在manifest檔案中設定Activity的主題。
<application
    ...
    android:theme="@android:style/Theme.Holo.NoActionBar.Fullscreen" >
    ...
</application>

通過設定主題來隱藏狀態列的優勢:
1.易於維護,因為這種方式不像動態設定那樣容易出錯。
2.介面切換更流暢,因為在初始化Activity之前,系統已經得到了渲染UI的資訊。

另外也可以動態隱藏狀態列。示例程式碼如下。
public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // If the Android version is lower than Jellybean, use this call to hide
        // the status bar.
        if (Build.VERSION.SDK_INT < 16) {
            getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                    WindowManager.LayoutParams.FLAG_FULLSCREEN);
        }
        setContentView(R.layout.activity_main);
    }
    ...
}

當設定了WindowManager之後(無論是通過主題還是動態設定),效果會一直存在直到手動清除。

(2).在4.1及以上版本中隱藏狀態列
在Android 4.1及以上版本中,可以使用setSystemUiVisibility()方法來隱藏狀態列。setSystemUiVisibility()在View層面設定UI的標識,然後這些設定被整合到Window層面。程式碼如下。
View decorView = getWindow().getDecorView();
// Hide the status bar.
int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN;
decorView.setSystemUiVisibility(uiOptions);
// Remember that you should never show the action bar if the
// status bar is hidden, so hide that too if necessary.
ActionBar actionBar = getActionBar();
actionBar.hide();

注意:
1.一旦設定的UI標識被清除(比如跳轉到另一個Activity),如果還需要隱藏狀態列就必須再次設定。
2.在不同的地方設定UI標識是有區別的。如果在Activity的onCreate()方法中隱藏狀態列,當用戶按下home鍵系統欄就會重新顯示。當用戶重新開啟Activity時,onCreate()方法不會被呼叫,所以狀態列還是可見的。如果想在不同的Activity之間切換時,可見性保持不變,需要在onResume()與onWindowFocusChaned()裡設定UI標識。
3.setSystemUiVisibility()只在被呼叫的View顯示時才會生效。
4.當從當前檢視轉到別的頁面時,setSystemUiVisibility()設定的標識會被清除。

(3).讓內容顯示在狀態列之後
在Android 4.1及以上版本,可以將應用的內容顯示在狀態列之後,這樣當狀態列顯示與隱藏時,內容區域的大小就不會發生變化。要達到這個效果,需要用到SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN這個標識。同時,也可以使用SYSTEM_UI_FLAG_LAYOUT_STABLE這個標誌來幫助應用維持一個穩定的佈局。

當使用這種方法時,你就需要來確保應用中特定區域不會被系統欄覆蓋(比如地圖應用中一些自帶的操作區域)。如果被覆蓋了,應用可能就會無法使用。在大多數的情況下,可以在佈局檔案中新增android:fitsSystemWindows屬性並設定為true。它會調整父ViewGroup使它留出特定區域給系統欄,對於大多數應用這種方法就足夠了。

在一些情況下,可能需要修改預設的padding大小來獲取合適的佈局。為了控制內容區域的佈局相對系統欄(系統欄佔據了一個叫做content insets的區域)的位置,可以重寫fitSystemWindows(Rect insets)方法。當視窗的內容嵌入區域發生變化時,fitSystemWindows()方法會被view層級呼叫,讓View做出相應的調整適應。重寫這個方法你就可以按你的意願處理嵌入區域與應用的佈局。

三、隱藏導航欄(Navigation Bar)

Android在4.0版本之後,引入了對導航欄隱藏的操作。



(1).在4.0及以上版本中隱藏導航欄
在Android 4.0及以上版本中,可以使用SYSTEM_UI_FLAG_HIDE_NAVIGATION標識來隱藏導航欄。下面這段程式碼同時隱藏了導航欄和狀態列。
View decorView = getWindow().getDecorView();
// Hide both the navigation bar and the status bar.
// SYSTEM_UI_FLAG_FULLSCREEN is only available on Android 4.1 and higher, but as
// a general rule, you should design your app to hide the status bar whenever you
// hide the navigation bar.
int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
              | View.SYSTEM_UI_FLAG_FULLSCREEN;
decorView.setSystemUiVisibility(uiOptions);

注意:
1.使用該方法時,觸控式螢幕幕的任何區域都會使導航欄(和狀態列)重新顯示。使用者的互動會使SYSTEM_UI_FLAG_HIDE_NAVIGATION標識被清除。
2.一旦該標識被清除,如果要再次隱藏導航欄,需要重新設定標識。
3.在不同的地方設定UI標識是有區別的。如果在Activity的onCreate()方法中隱藏系統欄,當用戶按下home鍵系統欄就會重新顯示。當用戶重新開啟Activity時,onCreate()方法不會被呼叫,所以狀態列還是可見的。如果想在不同的Activity之間切換時,可見性保持不變,需要在onResume()與onWindowFocusChaned()裡設定UI標識。
4.setSystemUiVisibility()只在被呼叫的View顯示時才會生效。
5.當從當前檢視轉到別的頁面時,setSystemUiVisibility()設定的標識會被清除。

(2).讓內容顯示在導航欄之後
在Android 4.1及以上版本中,可以將應用的內容顯示在導航欄的後面,這樣當導航欄顯示與隱藏時,內容區域的大小就不會發生變化。要達到這個效果,需要用到SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION這個標識。同時,也可以使用SYSTEM_UI_FLAG_LAYOUT_STABLE這個標誌來幫助應用維持一個穩定的佈局。

當使用這種方法的時候,就需要你來確保應用中特定區域不會被系統欄覆蓋。

四、使用沉浸式模式(Immersive Full-Screen Mode)

Adnroid 4.4為setSystemUiVisibility()方法引入了一個新的標識SYSTEM_UI_FLAG_IMMERSIVE,它可以讓應用進入真正的全屏模式。當該標識與SYSTEM_UI_FLAG_HIDE_NAVIGATION和SYSTEM_UI_FLAG_FULLSCREEN一起使用時,導航欄和狀態列就會隱藏,讓應用可以接受螢幕上任何區域的觸控事件。

當沉浸式全屏模式啟用的時候,你的Activity會繼續接受各類的觸控事件。使用者可以通過在邊緣區域向內滑動來讓系統欄重新顯示,這個操作清空了SYSTEM_UI_FLAG_HIDE_NAVIGATION和SYSTEM_UI_FLAG_FULLSCREEN兩個標識,因此係統欄重新變得可見。如果標識被設定了,這個操作同時也觸發了View.OnSystemUiVisibilityChangeListener監聽器。然而, 如果需要讓系統欄在一段時間後自動隱藏的話,應該使用SYSTEM_UI_FLAG_IMMERSIVE_STICKY標籤。注意,帶有'sticky'的標識不會觸發任何監聽器,因為在這種模式下顯示的系統欄只有短暫的停留時間。


在上圖中:
圖1.非沉浸模式。該模式展示了應用程式在進入沉浸模式之前的狀態,同時也是在設定了SYSTEM_UI_FLAG_IMMERSIVE標識後用戶滑動重寫顯示出系統欄的狀態。當用戶滑動後,SYSTEM_UI_FLAG_HIDE_NAVIGATION和SYSTEM_UI_FLAG_FULLSCREEN就會被清除,系統欄就會重新顯示並保持可見。 
注意,最好讓所有的UI控制元件的變化與系統欄的顯示隱藏保持同步,這樣可以減少螢幕顯示所處的狀態,同時提供了更無縫平滑的使用者體驗。因此所有的UI控制元件跟隨系統欄一同顯示。一旦應用進入了沉浸模式,相應的UI控制元件也跟隨著系統欄一同隱藏。為了確保UI的可見性與系統欄保持一致,可以新增View.OnSystemUiVisibilityChangeListener來監聽系統欄的顯示隱藏變化。
圖2.提示氣泡。第一次進入沉浸模式時,系統將會顯示一個提示氣泡,提示使用者如何再讓系統欄顯示出來。
圖3.沉浸模式。在該模式下,系統欄和其他UI操作都被隱藏。可以通過設定IMMERSIVE和IMMERSIVE_STICKY標識來進入這個狀態。
圖4.STICKY標識。這是在設定了SYSTEM_UI_FLAG_IMMERSIVE_STICKY標籤時的狀態,使用者會向內滑動以展示系統欄。半透明的系統欄會臨時的進行顯示,一段時間後自動隱藏。滑動操作並不會清空任何標識,也不會觸發系統欄可見性的監聽器,因為暫時顯示的導航欄並不被認為是一種可見性狀態的變化。

注意:IMMERSIVE標識只有在與SYSTEM_UI_FLAG_HIDE_NAVIGATION,SYSTEM_UI_FLAG_FULLSCREEN中一個或兩個一起使用的時候才會生效。雖然可以只使用其中的一個,但是一般情況下需要同時隱藏狀態列和導航欄以達到沉浸式效果。

(1).選擇一種沉浸方式
SYSTEM_UI_FLAG_IMMERSIVE和SYSTEM_UI_FLAG_IMMERSIVE_STICKY標識都提供了沉浸式的體驗,下面通過一些示例講解這兩種標識該如何選擇。
1.如果是在圖書、新聞或雜誌閱讀器應用中,可以將IMMERSIVE標識與SYSTEM_UI_FLAG_FULLSCREEN和SYSTEM_UI_FLAG_HIDE_NAVIGATION一起使用。因為使用者可能會經常操作Action Bar和一些UI控制元件,又不希望在翻頁時有其他的東西進行干擾,IMMERSIVE在是個很好的選擇。
2.如果需要打造一款真正的沉浸式應用,而且希望螢幕邊緣的區域也可以與使用者進行互動,並且使用者也不會經常訪問系統欄,這個時候可以將IMMERSIVE_STICKY和SYSTEM_UI_FLAG_FULLSCREEN、SYSTEM_UI_FLAG_HIDE_NAVIGATION兩個標識一起使用。比如一款遊戲或者繪圖應用就很合適。
3.如果是一個視訊播放器應用,需要少量的使用者互動操作,簡單的使用SYSTEM_UI_FLAG_FULLSCREEN與SYSTEM_UI_FLAG_HIDE_NAVIGATION就足夠了,不需要使用IMMERSIVE標籤。

(2).使用IMMERSIVE沉浸模式
當使用SYSTEM_UI_FLAG_IMMERSIVE標識時,它是基於其他設定過的標識SYSTEM_UI_FLAG_HIDE_NAVIGATION和SYSTEM_UI_FLAG_FULLSCREEN來隱藏系統欄的。當用戶觸控向內滑動,系統欄重新顯示並保持可見。

用其他的UI標識(如SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION和SYSTEM_UI_FLAG_LAYOUT_STABLE)來防止系統欄隱藏時內容區域大小發生變化是一種很好的方法,同時也需要確保Action Bar和其他系統UI控制元件同時隱藏。下面這段程式碼展示瞭如何在不改變內容區域大小的情況下,隱藏與顯示狀態列和導航欄。
// This snippet hides the system bars.
private void hideSystemUI() {
    // Set the IMMERSIVE flag.
    // Set the content to appear under the system bars so that the content
    // doesn't resize when the system bars hide and show.
    mDecorView.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 // hide nav bar
            | View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar
            | View.SYSTEM_UI_FLAG_IMMERSIVE);
}

// This snippet shows the system bars. It does this by removing all the flags
// except for the ones that make the content appear under the system bars.
private void showSystemUI() {
    mDecorView.setSystemUiVisibility(
            View.SYSTEM_UI_FLAG_LAYOUT_STABLE
            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
            | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
}

(3).使用IMMERSIVE_STICKY沉浸模式

當使用SYSTEM_UI_FLAG_IMMERSIVE_STICKY標識時,向內滑動的操作會讓系統欄臨時顯示,並處於半透明的狀態。此時沒有標識會被清除,系統欄可見性監聽器也不會被觸發。如果使用者沒有進行操作,系統欄會在一段時間內自動隱藏。


下面的示例程式碼,在視窗獲取焦點時,使用IMMERSIVE_STICKY沉浸模式。根上面的方法一樣,只是將IMMERSIVE修改為IMMERSIVE_STICKY。
@Override
public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
    if (hasFocus) {
        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);
    }
}

五、監聽系統欄的顯示和隱藏變化
對View設定View.OnSystemUiVisibilityChangeListener監聽器,可以監聽系統欄的顯示和隱藏變化。
View decorView = getWindow().getDecorView();
decorView.setOnSystemUiVisibilityChangeListener
        (new View.OnSystemUiVisibilityChangeListener() {
    @Override
    public void onSystemUiVisibilityChange(int visibility) {
        // Note that system bars will only be "visible" if none of the
        // LOW_PROFILE, HIDE_NAVIGATION, or FULLSCREEN flags are set.
        if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
            // TODO: The system bars are visible. Make any desired
            // adjustments to your UI, such as showing the action bar or
            // other navigational controls.
        } else {
            // TODO: The system bars are NOT visible. Make any desired
            // adjustments to your UI, such as hiding the action bar or
            // other navigational controls.
        }
    }
});

保持系統欄和應用程式的UI同步是一種很好的方式,比如當狀態列顯示或隱藏的時候進行ActionBar的顯示和隱藏等。