×

管理System UI (状态栏 + 导航栏)

96
ShenJC
2016.07.24 15:32* 字数 3848

概述

系统栏是专注于显示通知,设备状态的通信和设备导航的屏幕区域。典型的系统栏(由状态栏和导航栏组成,如下图)与应用程序同时显示。
应用程序显示沉浸式的内容时,比如电影或者照片,可以临时地变暗系统栏图标从而让人减少分心,或者为了一个完全的沉浸式体验而隐藏系统栏。

本文主要讲解怎样去调暗或者隐藏系统栏,根据不同的Android版本去创建一个沉浸式的用户体验,与此同时依旧保留对系统栏的便捷访问。

system-ui.png

调暗系统栏

调暗系统栏只支持在Android 4.0 (API level 14) 和以上。在4.0以前版本,Android 不提供内置的方法来调暗。

使用这种方式,当前内容不会调整大小,但是系统栏上的图标会在视觉上减弱。只要用户触碰了状态栏或者导航栏的区域时,那么状态栏和导航栏会立马变成全部显示状态。这种方式的优势是系统栏依旧存在但是他们的细节被遮盖了,那么我们既可以实现沉浸式体验又可以方便的访问系统栏。

调暗状态栏和导航栏

你可以通过调用 flag 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);

一旦用户触碰了状态栏和导航栏区域, 这个flag就会被清除,从而系统栏就会显示出来。只要这个flag被清除了,你的app就需要重新设置这个flag来再次调暗系统栏。

显示状态栏和导航栏

如果你想要手动的清除flags,可以通过 setSystemUiVisibility(), 如下:

View decorView = getActivity().getWindow().getDecorView();
// Calling setSystemUiVisibility() with a value of 0 clears
// all flags.
decorView.setSystemUiVisibility(0);

隐藏 Status Bar

隐藏状态栏(导航栏可选)让内容使用更多的显示空间,从而提供一个更好的沉浸式的用户体验。

图一:显示一个app带有可见的状态栏

**Figure 1.** Visible status bar.

图二:一个隐藏了状态栏的app,记住action bar 也一起隐藏了。

当status bar 隐藏的时候,action bar也应该一起隐藏。

**Figure 2.** Hidden status bar

在Android 4.0 及以下的系统中,隐藏Status Bar

在Android4.0(API level 14)及以下版本中你可以通过设置 [WindowManager] (https://developer.android.com/reference/android/view/WindowManager.html) 的flags来隐藏status bar。你可以以编程的方式来实现或者通过在manifest中设置一个activity的主题来实现。如果status bar应该总是保持隐藏的状态,那么设置activity主题是首选的方式。比如:

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

使用activity主题的优势有:

  • 比编程设置flag 更容易维护 和 更少出错
  • 它会导致更平滑的UI过渡,因为系统会在在实例化你的应用主activity之前来渲染你的UI。

或者,通过编程的方式设置 WindowManager flags。这种方式当用户与你的app交互的时候可以更方便的隐藏和显示状态栏。

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 (不管是通过activity 主题还是编程), 这个flags 将保持影响直到你的app清除他们。你可以使用FLAG_LAYOUT_IN_SCREEN来设置Activity布局使用相同的屏幕区域,不过当你同时启用了 FLAG_FULLSCREEN flag时,这才会起作用。 当状态栏隐藏和显示的时候,这个会阻止你的内容改变大小。

在Android 4.1 及以上系统中隐藏Status Bar

在Android 4.1 (API level 16) 及以上的系统上,你可以在个别view 级别上通过使用 setSystemUiVisibility() 设置UI flags;这些设置会聚合到window级别。使用setSystemUiVisibility() 来设置UI flags比起使用 WindowManager flags给你更细颗粒的控制system bras。
以下代码隐藏status bar:

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

记住以下几点:

  • 一旦UI flags被清除了,你的app需要重新设置这些flags。查看Responding to UI Visibility Changes 来如何监听并响应UI 可见度变化的事件。

  • 设置 UI flags 的时机不同会产生不同的效果。如果你在 activity的 onCreate() 方法中隐藏了系统栏,然后用户按了Home键,系统栏会重新显示出来。当用户重新打开这个activity的时候,onCreate()不会再被调用了,所以系统栏将保持可见状态。如果你想要在进出activity时保持system UI 的改变状态, 在 onResume() 或者 onWindowFocusChanged()方法中设置UI flags。

  • 只有当调用者的View是可见的,方法setSystemUiVisibility() 才会有效。

  • 当从这个View导航离开的时候,通过setSystemUiVisibility() 设置的flags 会被清除。

把内容藏到状态栏后面

在Android4.1及以上版本中,你可以把应用的内容显示在状态栏后面,所以当status bar隐藏和显示的时候,内容区域不会改变大小。想要到达这个效果,可以使用SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN。你同样也需要使用 SYSTEM_UI_FLAG_LAYOUT_STABLE 来帮助你的app来维持一个稳定的布局。

当你使用了这种方式, 你的职责就是确保app UI的主要部分 (比如,在地图应用中内置的控制) 不会因为被system bars 覆盖而结束。 这会导致你的app不能用。在大多数情况下你可以控制它通过在XML 布局文件中添加 theandroid:fitsSystemWindows 属性,设置为true。这个会调节 父 ViewGroup的padding值来给system windows腾出空间。对大多数应用这已经足够了。

但是还有另外一些情况 你可能需要修改默认的padding来得到你渴望的布局。直接相对于system bars来操作你的content布局(这个空间称为window's "content insets"), 覆写fitSystemWindows(Rect insets)
. 这个方法fitSystemWindows() 会被view hierarchy 调用当 一个window的content insets 变化的时候,来允许window相应的调整它的内容区域。通过覆写这个方法你可以操作这个insets来达到你想要的效果。

隐藏 Navigation Bar

在隐藏Navigation Bar的同时,你应该设计你的app同时也隐藏掉status bar,像 Hiding the Status Bar提到的。

navigation-bar.png

隐藏 Navigation Bar 的实现

通过设置 SYSTEM_UI_FLAG_HIDE_NAVIGATION flag 可以隐藏Navigation Bar。下面的代码同时隐藏了navigation bar 和 status 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);

记住以下几点:

  • 使用这种方式,触碰屏幕上的任意地方都会造成navigation bar 和 status bar重新显示并保持可见。flags会被清除。

  • 一旦UI flags被清除了,你的app需要重新设置这些flags。查看Responding to UI Visibility Changes 来讨论怎样监听UI 显示变化的事件。

  • 设置 UI flags 的时机不同会产生不同的效果。如果你在 activity的 onCreate() 方法中隐藏了system bars,然后用户按了Home键,system bars 会重新显示出来。当用户重新打开这个activity的时候,onCreate()不会再被调用了,所以system bars将保持可见状态。如果你想要在进出activity时保持system UI 的改变状态, 在 onResume() 或者 onWindowFocusChanged()方法中设置UI flags。

  • 只有当调用者的View是可见的,方法setSystemUiVisibility() 才会有效。

  • 当从这个View导航离开的时候,通过setSystemUiVisibility() 设置的flags 会被清除。

把内容藏在Navigation Bar 背后

在Android4.1及以上版本,你可以把程序的内容显示在Navigation bar后面,所以内容区域不会改变大小当status bar隐藏和显示。想要到达这个效果,可以使用SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN。你同样也需要使用SYSTEM_UI_FLAG_LAYOUT_STABLE 来帮助你的app来维持一个稳定的布局。
当你使用了这种方式, 你的职责就是确保app UI的主要部分 (比如,在地图应用中内置的控制) 不会因为被系统栏覆盖而结束。更多细节请参考 [把内容出现在Status Bar 后面](#把内容藏在Status Bar 后面).

使用沉浸式全屏模式

Android 4.4 (API Level 19) 中给setSystemUiVisibility()添加了一个新的属性SYSTEM_UI_FLAG_IMMERSIVE来帮助app实现真正的"全屏"效果。当这个flag结合SYSTEM_UI_FLAG_HIDE_NAVIGATION
SYSTEM_UI_FLAG_FULLSCREEN flags时, 会隐藏导航栏和状态栏来让你的app接收所有的触碰事件。

当沉浸式的全屏模式开启的时候,你的activity会持续的接收所有的触碰事件。如果用户想要显示系统栏,可以通过在系统栏正常显示的位置向内滑动。这样会清除 flag SYSTEM_UI_FLAG_HIDE_NAVIGATION (和 SYSTEM_UI_FLAG_FULLSCREEN
flag, if applied) ,所以系统栏会变成可见的。如果设置了监听函数,那么就会触发你的 View.OnSystemUiVisibilityChangeListener。但是,如果你想要系统栏在几秒后再次自动隐藏,你可以使用flag SYSTEM_UI_FLAG_IMMERSIVE_STICKY来代替。记住带有"sticky" 字段的flags 不会触发任何 listeners, 因为system bars只是临时处于这种模式中。

**Figure 1.** Immersive mode states.

在图1中:

  1. 非 沉浸式— 这是app 进入沉浸式前的状态。同样也是这样呈现,如果你使用了 flag IMMERSIVE 并且用户滑动去显示system bars。从而清除 flag SYSTEM_UI_FLAG_HIDE_NAVIGATION
    SYSTEM_UI_FLAG_FULLSCREEN。一旦这些flags 被清除,system bars 会重新出现并保持可见状态。

  2. 提醒气泡— 当用户第一次进去app的沉浸式模式的时候,系统会显示一个提醒气泡。这个气泡提醒用户怎样显示系统栏。

Note: 如果你想在测试目的中强制出现提醒气泡,你可以通过这种方式把app设置到沉浸式模式,关闭屏幕,然后在5s之内打开屏幕。

  1. 沉浸式— app在沉浸式模式,状态栏 和其他控制界面被隐藏。可以通过设置 flags IMMERSIVE 或者 IMMERSIVE_STICKY 来达到这个状态。

  2. Sticky flag— 这是使用flag IMMERSIVE_STICKY,并且用户滑动后显示了system bars。半透明的system bars会短暂的出现然后再次消失。这种行为不会清除任何的flags,监听system UI 可见性变化的listeners并不会被调用,因为system bars的短暂出现并不认为是一种UI可见性的变化。

Note: 记住 "immersive" 相关的flags只有当你结合使用了 SYSTEM_UI_FLAG_HIDE_NAVIGATIONSYSTEM_UI_FLAG_FULLSCREEN 它们中的一个或者一起,才起作用。 当你实现了 "full immersion" 模式的时候,常见的行为是同时隐状态栏和导航栏。

选择一个方法

Flags SYSTEM_UI_FLAG_IMMERSIVESYSTEM_UI_FLAG_IMMERSIVE_STICKY 都提供一个沉浸式的体验,但是在行为上有点分歧。参与以下原则来进行选择:

  • 如果你想创建一个视频播放器或者其他需要极少用户交互的app,你大概可以从 lean back 方式中得到, 从 Android 4.0 (API Level 14)开始有效。这种类型,简单的使用flag SYSTEM_UI_FLAG_FULLSCREEN
    SYSTEM_UI_FLAG_HIDE_NAVIGATION 。这种情景下不要使用 "immersive"相关的flags。

使用非粘性的沉浸式

当你使用了flag SYSTEM_UI_FLAG_IMMERSIVE, 它会结合你设置的其他UI flag (SYSTEM_UI_FLAG_HIDE_NAVIGATION
, SYSTEM_UI_FLAG_FULLSCREEN
, or both)隐藏系统栏。当用户在系统栏区域向内滑动时,系统栏会重新显示并保持可见。

通常我们会包含其他的system UI flags (比如 SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
SYSTEM_UI_FLAG_LAYOUT_STABLE) ,当system bars隐藏和显示的时候用来防止内容大小改变。你应该保证 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);
}

你可以让 flag [IMMERSIVE] (https://developer.android.com/reference/android/view/View.html#SYSTEM_UI_FLAG_IMMERSIVE) 结合以下选项,来提供更好的用户体验:

关于这个主题的更多讨论,请看视频 DevBytes: Android 4.4 Immersive Mode

使用粘性的沉浸式

当使用了 flag SYSTEM_UI_FLAG_IMMERSIVE_STICKY, 在系统栏的区域中向内滑动会引起系统栏出现一个半透明的状态,但是 flags 不会被清除,监听系统界面可见性变化的listeners并不会被调用。系统栏会再次动态隐藏在几秒之后,同样当用户点击了屏幕,系统栏也会隐藏。

图片 2 显示半透明的状态栏,短暂的出现然后再次消失,使用了flag IMMERSIVE_STICKY

**Figure 2.** Auto-hiding system bars

当window获得焦点的时候,简单地设置flag IMMERSIVE_STICKY, 可以和其他在 Use IMMERSIVE中提到的flags一起使用.
比如:

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

响应UI 可见度的变化

可以通过注册一个listener来监听System UI 可见度的变化。

注册一个listener

为了获取system UI 可见度变化的通知,注册一个 View.OnSystemUiVisibilityChangeListener 到你的view中。这个view就是你用来控制导航可见度的View。
举个例子,在 activity 的 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.
        }
    }
});

当系统栏可见度发生变化的时候,这是一个很好的方式来同步你的界面。例如,你可以使用这个listener来隐藏和显示action bar来保持与状态栏的显示状态保持一致。

实例代码

我们可以参考Android官方的api demo中提供的SystemUI demo

Android - Training
Web note ad 1