[Android] 關於系統工具欄和全屏沉浸模式

關於System Bars,之前寫過幾篇相關的文章:(鏈接等我把博客遷移好之後補上)

這三篇是按順序寫的,本來只是項目上的應用,其實並不需要深究的,查到方法並能用起來就好。隨着應用程序的一些深入設計,大家總想要更好的界面和體驗,所以有些東西並不能只是知道方法就結束了,是得要去深入研究研究的。通過這個過程我覺得,從應用層面來講,想實現一個功能很簡單,但若想實現的好,就要去了解設計者的設計思路以及提供的方法。而瞭解設計者想法最直接的途徑就是查看文檔。當然,瞭解文檔以後還可以再進一步,看看 Android 的源碼是怎麼實現的,也就是從 Application 層進入到 Framework 層。熟悉 Framework 後就可以配合着 Linux 內核的知識瞭解 Android 底層的實現了。好了,先把注意放在應用層,畢竟這是最簡單的。

System Bars 包括三條 bar:

  • status bar,也就是頂部的一條顯示時間、電量、通知等信息的 bar
  • Navigation Bar,底部包含 back 鍵、home 鍵以及 recent 鍵的 bar
  • action bar,程序內頂部的可以添加諸如 search、menu 的 bar

  對 System Bar 的 操作也就是獲取高度、狀態以及設置顯示/隱藏狀態,前兩者之前寫過了,這次就把隱藏這些 bar 寫詳細點。


淡化系統工具欄

  淡化(dim—不知道這麼譯合適不)工具欄的效果就是 status bar 和 navigation bar 上的圖標都變成一個淡灰色的圓點。這麼做的意義就是可以讓用戶目光的焦點集中在程序要顯示的內容上面,避免了屏幕上過多的東西分散用戶注意力。

  可能這麼說起來感覺這麼做沒有太大的意義,但實際上用戶體驗就是各方面一點點的細節積累起來的。有些時候用戶在比較幾款 APP 的時候都會有很明顯的喜歡哪個不喜歡哪個,但讓他具體列出來差距在哪裏他卻列不出來。這其中的原因大多就在這些小細節上,說不出但能感覺的到。而且既然有這個功能,它便有存在的意義,那麼就來了解了解它怎麼實現的。

  注意這個方法只在4.0版本及以上適用。使用時,應用內容顯示的尺寸不會變化,只會把兩條 bar 上的圖標變淡,一旦觸摸 bar 的區域,所有圖標就會顯現出來,不再消失。

  方法很簡單,設置 system flag 爲 SYSTEM_UI_FLAG_LOW_PROFILE 即可

getActivity().getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);

  注意一旦觸摸 bar 的位置,這個 flag 就會被清空,所以觸摸結束後圖標也不會淡化了。如果需要繼續實現淡化效果,可以用 View.OnSystemUiVisibilityChangeListener 來監聽狀態變化再做處理。

  如果想通過代碼在某些情況下主動清除當前 system ui flag ,可以用:

getActivity().getWindow().getDecorView().setSystemUiVisibility(0);

  看過我之前文章的可以知道,0 代表默認狀態,也就是 bar 都正常顯示的 flag,即:

public static final int SYSTEM_UI_FLAG_VISIBLE = 0;

隱藏 Status Bar

  其實淡化用的不是很多,而隱藏 Status Bar 倒是比較多。因爲可以釋放更多的顯示空間,可以提供更好的用戶體驗。

  下面兩張圖可以看到隱藏 status bar 讓程序更直觀簡潔,看起來更舒服。

  注意,左邊的圖帶有 action bar,如果你不顯示 status bar 的時候也要把 action bar 隱藏掉,這是設計界面的建議。 設置方法:

4.0及以下版本: : 1. 在 manifest 中設置:

<application
    ...
 android:theme="@android:style/Theme.Holo.NoActionBar.Fullscreen" >
    ...
</application>

這樣設置比較簡單而且不易出錯,因爲系統在實例化 mainActivity 前已經擁有要渲染的界面,所以UI轉換會比較平滑 : 2. 在代碼中用 WindowManager flags 設置

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 flags 後這個 flag 會一直保留直到用代碼清理掉他們。如果已經設定 FLAG_FULLSCREEN,就可以用 FLAG_LAYOUT_IN_SCREEN 設置 activity layout 使用當前可用的屏幕區域,這個 flag 可以防止顯示/隱藏 status bar 時界面尺寸變化。

4.1及以上版本: : 可以用前面提到的 setSystemUiVisibility() 在單獨 view 層級上設置 UI 的標誌,這些標誌在窗口上生效。

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. 設置的 flag 一旦清空,應用程序需要重新設置 flag 才能隱藏 bar 。添加 listener 做處理即可
  2. 設置 flag 的代碼寫在不同的地方有不同的效果。比如你在 activity 的 onCreate() 方法裏設置隱藏的標誌,用戶按下 Home 鍵, status bar 會再度顯示,之後再打開應用程序,status bar 會保持顯示的狀態。如果需要其隱藏掉,需要在 onResume() 或者 onWindowFocusChanged() 方法裏設置。
  3. setSystemUiVisibility() 方法只在可見的 view 中設置纔有效,比如設置 View.gone 就沒有效果
  4. 切換 view 會把當前 view 設置的 flag 清空

將程序內容顯示在 Status Bar 的後面 之前的文章遇到過這個問題,還困擾了我半天,後來才發現程序是可以顯示在 status bar 的後面的,這樣的好處是程序的內容尺寸不會隨着 status bar 的顯示和隱藏而改變。 實現這個效果只用設置 SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN ,同時用 SYSTEM_UI_FLAG_LAYOUT_STABLE 來保證尺寸不變即可 When you use this approach, it becomes your responsibility to ensure that critical parts of your app’s UI (for example, the built-in controls in a Maps application) don’t end up getting covered by system bars. This could make your app unusable. In most cases you can handle this by adding the android:fitsSystemWindows attribute to your XML layout file, set to true. This adjusts the padding of the parent ViewGroup to leave space for the system windows. This is sufficient for most applications. In some cases, however, you may need to modify the default padding to get the desired layout for your app. To directly manipulate how your content lays out relative to the system bars (which occupy a space known as the window’s “content insets”), override fitSystemWindows(Rect insets). The fitSystemWindows() method is called by the view hierarchy when the content insets for a window have changed, to allow the window to adjust its content accordingly. By overriding this method you can handle the insets (and hence your app’s layout) however you want.


隱藏 Navigation Bar

  作爲設計上的建議,在隱藏掉導航欄的同時,也要把狀態欄隱藏掉(當然狀態欄隱藏了也要把動作欄也隱藏掉),當然隱藏掉還是保持隨時可喚出的,這樣可以利用整個屏幕空間,給用戶更棒的體驗。   在4.0及以上版本使用 SYSTEM_UI_FLAG_HIDE_NAVIGATION 設置同時隱藏 status bar 和 navigation bar。  

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

注意事項參考隱藏 status bar 的注意。

當然,既然可以讓程序內容顯示在 status bar 的後面,那麼相同效果也可以在 Navigation bar 上設置。使用 SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 和 SYSTEM_UI_FLAG_LAYOUT_STABLE 即可。


使用全屏沉浸模式

  這是4.4版本新加的模式,設置標誌爲 SYSTEM_UI_FLAG_IMMERSIVE 和 SYSTEM_UI_FLAG_IMMERSIVE_STICKY兩種。經常配合着 SYSTEM_UI_FLAG_HIDE_NAVIGATION 和 SYSTEM_UI_FLAG_FULLSCREEN 使用。

(補充:FLAG_IMMERSIVE 要和 FLAG_HIDE_NAVIGATION and FLAG_FULLSCREEN 兩者其一一起使用纔有效,與前者用爲隱藏下方的 bar,與後者用爲隱藏上方的 bar)

  這個模式的效果爲隱藏掉上下兩條 bar,同時你在 bar 的範圍內點擊事件也不會將其喚出,這爲程序的操作提供了很大的便利。你會問,既然點擊事件不會喚出 bar,那我要是想用 bar 上的功能怎麼辦?這個也很簡單,手指放在 bar 的區域,如果是 status bar 的區域則手指向下滑動,反之則向上滑動,這樣就可以把兩條 bar 喚出了。這個操作實際上是把 SYSTEM_UI_FLAG_HIDE_NAVIGATION 和 SYSTEM_UI_FLAG_FULLSCREEN 清掉了,所以纔會可見,同時會觸發對應的 Listener 。

  前面說了有兩種 IMMERSIVE 和 IMMERSIVE_STICKY ,前者是將 bar 喚出後不再消失,後者是將 bar 喚出後幾秒就消失,後者不觸發 Listener。

  還有一點,設置 FULLSCREEN 會讓 status bar 顯示的時候背景爲半透明,正常狀態下 status bar 的背景是黑色的。見下圖:

圖1:正常狀態。圖2:第一次進入 immersive full-screen mode 時會有提示。

關於 IMMERSIVE 和 IMMERSIVE_STICKY 的選擇:

  • 如果是做一個看書、雜誌軟件或者看新聞軟件,建議使用 IMMERSIVE 標誌,配合 SYSTEM_UI_FLAG_FULLSCREEN 和 SYSTEM_UI_FLAG_HIDE_NAVIGATION 。因爲用戶可能會頻繁需要用到 UI 按鈕,同時在瀏覽內容的時候不希望被打擾。
  • 如果希望用戶體驗沉浸模式,那就用 STICKY 標誌
  • 如果像視頻播放器那樣用戶交互就很少,就不要用 IMMERSIVE 了,之前寫的內容就可以滿足需求

  使用 IMMERSIVE 標誌時,隱藏的 bar 會一直顯示,那麼就需要設置一些標誌保持軟件內容尺寸不變,如SYSTEM_UI_FLAG_HIDE_NAVIGATION 和 SYSTEM_UI_FLAG_LAYOUT_STABLE,同時也要注意 action bar 的隱藏。

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

  下面是程序窗口收到焦點時設置 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.OnSystemUiVisibilityChangeListener 來使界面同步變化,可以在 onCreate() 方法中添加以下代碼:

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

以上可能有理解上誤差或者我測試中的沒發現的錯誤,如果您看過後發現有哪些問題請留下反饋,謝謝。

Written with StackEdit.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章