android ui可见_在android 11中修改系统ui可见性

本文介绍了如何在Android 11操作系统中改变系统的用户界面(UI)可见性。通过引用原文档,读者可以了解到具体的操作步骤和实现细节。
摘要由CSDN通过智能技术生成

android ui可见

When developing Android apps sometimes there is a requirement that the system UI must be hidden and show app in fullscreen. A typical example is a camera app or barcode scanner. In this article I will show how to achieve this with the new WindowInset API, as well as the legacy View.systemUiVisibility so that the code works in Android 11 and is backward compatible with older versions.

开发Android应用程序时,有时要求必须隐藏系统UI并全屏显示应用程序。 一个典型的例子是相机应用程序或条形码扫描仪。 在本文中,我将展示如何使用新的WindowInset API以及旧版的View.systemUiVisibility实现此目的,以便该代码在Android 11中工作并向后兼容旧版本。

隐藏系统用户界面 (Hiding system UI)

Below, you can see an example of our camera screen which takes a photo for user’s avatar.

在下面,您可以看到我们的相机屏幕示例,该屏幕为用户的头像拍照。

We want to hide the system bars when the Activity is shown. We will create a set of Kotlin extension utility functions so that they can be reused anywhere in the app. First, let’s create a function which hides the UI:

当活动显示时,我们想隐藏系统栏。 我们将创建一组Kotlin扩展实用程序功能,以便可以在应用程序中的任何位置重用它们。 首先,让我们创建一个隐藏UI的函数:

/**
 * Hides the system bars and makes the Activity "fullscreen". If this should be the default
 * state it should be called from [Activity.onWindowFocusChanged] if hasFocus is true.
 * It is also recommended to take care of cutout areas. The default behavior is that the app shows
 * in the cutout area in portrait mode if not in fullscreen mode. This can cause "jumping" if the
 * user swipes a system bar to show it. It is recommended to set [WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER],
 * call [showBelowCutout] from [Activity.onCreate]
 * (see [Android Developers article about cutouts](https://developer.android.com/guide/topics/display-cutout#never_render_content_in_the_display_cutout_area)).
 * @see showSystemUI
 * @see addSystemUIVisibilityListener
 */
fun Activity.hideSystemUI() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        window.insetsController?.let {
            // Default behavior is that if navigation bar is hidden, the system will "steal" touches
            // and show it again upon user's touch. We just want the user to be able to show the
            // navigation bar by swipe, touches are handled by custom code -> change system bar behavior.
            // Alternative to deprecated SYSTEM_UI_FLAG_IMMERSIVE.
            it.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
            // make navigation bar translucent (alternative to deprecated
            // WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
            // - do this already in hideSystemUI() so that the bar
            // is translucent if user swipes it up
            window.navigationBarColor = getColor(R.color.internal_black_semitransparent_light)
            // Finally, hide the system bars, alternative to View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
            // and SYSTEM_UI_FLAG_FULLSCREEN.
            it.hide(WindowInsets.Type.systemBars())
        }
    } else {
        // Enables regular immersive mode.
        // For "lean back" mode, remove SYSTEM_UI_FLAG_IMMERSIVE.
        // Or for "sticky immersive," replace it with SYSTEM_UI_FLAG_IMMERSIVE_STICKY
        @Suppress("DEPRECATION")
        window.decorView.systemUiVisibility = (
                // Do not let system steal touches for showing the navigation bar
                View.SYSTEM_UI_FLAG_IMMERSIVE
                        // Hide the nav bar and status bar
                        or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                        or View.SYSTEM_UI_FLAG_FULLSCREEN
                        // Keep the app content behind the bars even if user swipes them up
                        or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                        or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
        // make navbar translucent - do this already in hideSystemUI() so that the bar
        // is translucent if user swipes it up
        @Suppress("DEPRECATION")
        window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
    }
}

As you can see, for Android 11 and later, the WindowInsetsController is used. The main caveat with the new API is that the default behavior is BEHAVIOR_SHOW_BARS_BY_TOUCH, which basically steals your app’s touches when navigation bar is hidden and a touch anywhere inside the app causes the navigation bar to reappear. However, we want to allow the user taking a photo or touching the camera view for changing focus without showing the bars. That’s why we set BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE. Hiding the bars is simply achieved by calling the hide() method. Since we want the content of the app to be visible under the bars, we also set our custom semitransparent color by setting Window’s navigationBarColor.

如您所见,对于Android 11及更高版本,使用了WindowInsetsController 。 新API的主要警告是,默认行为是BEHAVIOR_SHOW_BARS_BY_TOUCH ,当隐藏导航栏时,它基本上会窃取您应用程序的触摸,并且在应用程序中任何位置的触摸都会导致导航栏重新出现。 但是,我们希望允许用户拍照或触摸相机视图以改变焦点而无需显示条形图。 这就是为什么我们设置BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE的原因。 通过调用hide()方法可以简单地隐藏栏。 由于我们希望应用程序的内容在条形下可见,因此我们还通过设置Window的navigationBarColor来设置自定义半透明颜色。

For older versions of Android, we achieve hiding status bar by setting the correct mix of flags to the systemUiVisibility of the Window’s decorView. Similarly as above, the View.SYSTEM_UI_FLAG_IMMERSIVE is used for avoiding the system stealing touches. Hiding the navigation and status bar is achieved by View.SYSTEM_UI_FLAG_HIDE_NAVIGATION and View.SYSTEM_UI_FLAG_FULLSCREEN respectively. We also add View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION and View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN. These flags ensure the app content stays behind the bars if user swipes them up. We also add the WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION to the Window so that the navigation bar is semitransparent.

对于旧版本的Android,我们实现了通过标志的正确组合设置为隐藏状态栏systemUiVisibility窗口的的decorView 。 与上面类似, View.SYSTEM_UI_FLAG_IMMERSIVE用于避免系统窃取触摸。 隐藏导航和状态栏分别通过View.SYSTEM_UI_FLAG_HIDE_NAVIGATIONView.SYSTEM_UI_FLAG_FULLSCREEN实现。 我们还添加了View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATIONView.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 。 这些标记可确保用户向上滑动应用程序内容时不会出现任何问题。 我们还将WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION添加到窗口中,以便导航栏是半透明的。

打开活动时以全屏显示活动 (Showing Activity in fullscreen when it is opened)

In order to hide all the system UI automatically when the Activity is opened, it is necessary to call our hideSystemUI() extension function from onWindowFocusChanged() when the Activity’s Window gets the focus. Therefore we override the method in the given Activity as follows:

为了在打开“活动”时自动隐藏所有系统UI,有必要在“活动”的窗口获得焦点时从onWindowFocusChanged()调用我们的hideSystemUI()扩展函数。 因此,我们将给定Activity中的方法重写如下:

override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (hasFocus) hideSystemUI()
}

显示系统界面 (Showing system UI)

We also want to be able to show the system UI again. Our goal is to achieve showing system bars above the app as in the picture shown below.

我们还希望能够再次显示系统UI。 我们的目标是实现在应用程序上方显示系统栏,如下图所示。

Image for post

Let’s create a new function for showing the UI:

让我们创建一个用于显示UI的新功能:

/**
 * Shows the system bars and returns back from fullscreen.
 * @see hideSystemUI
 * @see addSystemUIVisibilityListener
 */
fun Activity.showSystemUI() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        // show app content in fullscreen, i. e. behind the bars when they are shown (alternative to
        // deprecated View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION and View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
        window.setDecorFitsSystemWindows(false)
        // finally, show the system bars
        window.insetsController?.show(WindowInsets.Type.systemBars())
    } else {
        // Shows the system bars by removing all the flags
        // except for the ones that make the content appear under the system bars.
        @Suppress("DEPRECATION")
        window.decorView.systemUiVisibility = (
                View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                        or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
    }
}

Showing the UI with the new API is achieved by calling WindowInsetsController’s show() method. We also call Window.setDecorFitsSystemWindows(false) so that the content is shown fullscreen under the bars and doesn’t jump.

通过调用WindowInsetsController的show()方法可以显示具有新API的UI。 我们还调用Window.setDecorFitsSystemWindows(false) ,以使内容在条形下全屏显示,并且不会跳转。

In older Android versions, we simply set the decorView’s systemUiVisibility to the combination of View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION and View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN flags. This clears the previous flags which hid the bars and at the same time achieves showing the app UI in the same way as if system bars were hidden, which results in showing app UI under the system bars.

在较旧的Android版本中,我们只需将decorView的systemUiVisibility设置为View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATIONView.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN标志的组合。 这将清除先前隐藏条形的标志,并同时以与隐藏系统条相同的方式实现显示应用程序UI,从而导致在系统条下显示应用程序UI。

处理切口 (Handling cutouts)

As you know, Android supports display cutouts on devices running Android 9 and higher. According to the official article in Android Developers website, the default behavior is that in portrait mode app is shown inside the cutout area unless View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION is set. This means that in Android 10 and lower the app content will “jump” into the cutout when one of the flags is cleared. See an example on the screenshot below.

如您所知,Android在运行Android 9及更高版本的设备上支持显示切口。 根据Android Developers网站上的官方文章,除非设置了View.SYSTEM_UI_FLAG_FULLSCREENView.SYSTEM_UI_FLAG_HIDE_NAVIGATION,否则默认行为是在肖像模式下的应用显示在剪切区域内。 这意味着在Android 10及更低版本中,清除其中一个标志后,应用程序内容将“跳入”切口。 请参见下面的屏幕截图中的示例。

Image for post

To avoid this, it is recommended to completely avoid showing the app content inside the cutout areas by using the LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER. So let’s create another extension function:

为避免这种情况,建议完全避免使用LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER在剪切区域内显示应用程序内容。 因此,让我们创建另一个扩展功能:

/**
 * Should be called from [Activity.onCreate].
 * @see hideSystemUI
 * @see showSystemUI
 */
fun Activity.showBelowCutout() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
        window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
    }
}

If we call this function from the given Activity’s onCreate(), the behavior is as we need, i. e. the app’s content doesn’t move if the system bars are shown:

如果我们从给定ActivityonCreate()调用此函数,则行为是我们需要的,即,如果显示系统栏,则应用程序的内容不会移动:

Image for post

用户输入后修改系统界面可见性 (Modifying system UI visibility after user’s input)

Finally, we want to toggle the visibility of the system bars after the user clicks some empty area. In order to do this, first we have to know the current status, because the bars can be shown / hidden not only explicitly by our extension functions, but also by the user swiping the bars.

最后,我们要在用户单击某个空白区域后切换系统栏的可见性。 为此,首先我们必须知道当前状态,因为不仅可以通过扩展功能显式显示/隐藏条,还可以通过用户滑动条来显式显示/隐藏条。

We create a new extension function which automatically registers a listener and takes care of the differences in the used API:

我们创建了一个新的扩展功能,该功能会自动注册一个侦听器并解决所用API中的差异:

/**
 * Registers a listener which is informed when UI is shown (true) or hidden (false).
 */
fun Window.addSystemUIVisibilityListener(visibilityListener: (Boolean) -> Unit) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        decorView.setOnApplyWindowInsetsListener { v, insets ->
            val suppliedInsets = v.onApplyWindowInsets(insets)
            // only check for statusBars() and navigationBars(), because captionBar() is not always
            // available and isVisible() could return false, although showSystemUI() had been called:
            visibilityListener(suppliedInsets.isVisible(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars()))
            suppliedInsets
        }
    } else {
        @Suppress("DEPRECATION")
        decorView.setOnSystemUiVisibilityChangeListener {
            visibilityListener((it and View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0)
        }
    }
}

In Android 11 and later, we use the decorView’s setOnApplyWindowInsetsListener. In this case we have to be careful, because the method’s Javadoc states that the given listener takes over the policy for applying window insets and the given View’s onApplyWindowInsets method will not be called. Since we only want to find out the current state without modifying the policy, we just manually call the View’s onApplyWindowInsets method passing the WindowInsets object. Finally, we use the WindowInsets.isVisible method for checking the system bars visibility status. There is one caveat though. In our showSystemUI() and hideSystemUI() extension functions we used WindowInsets.Type.systemBars(), which includes statusBars(), captionBar() and navigationBars(). However, if caption bar is not used, isVisible(WindowInsets.Type.systemBars()) returns false even if status and navigation bars are visible. That’s why check only statusBars() and navigationBars().

在Android 11及更高版本中,我们使用decorView的setOnApplyWindowInsetsListener 。 在这种情况下,我们必须小心,因为该方法的Javadoc声明给定的侦听器接管了应用窗口插入的策略,并且不会调用给定的View的onApplyWindowInsets方法。 由于我们只想查找当前状态而不修改策略,因此我们只是通过传递WindowInsets对象来手动调用View的onApplyWindowInsets方法。 最后,我们使用WindowInsets.isVisible方法检查系统栏的可见性状态。 有一个警告。 在我们的showSystemUI()hideSystemUI()扩展函数中,我们使用了WindowInsets.Type.systemBars() ,其中包括statusBars(),captionBar()navigationBars() 。 但是,如果不使用标题栏,则即使状态栏和导航栏可见, isVisible(WindowInsets.Type.systemBars())也会返回false 。 这就是为什么只检查statusBars()navigationBars()的原因

In the legacy code we just set listener via decorView’s setOnSystemUiVisibilityChangeListener and check whether the View.SYSTEM_UI_FLAG_HIDE_NAVIGATION flag is unset.

在旧版代码中,我们只需通过decorView的setOnSystemUiVisibilityChangeListener设置侦听器,然后检查View.SYSTEM_UI_FLAG_HIDE_NAVIGATION标志是否未设置。

Now, all we have to do is set the listener in the Activity:

现在,我们要做的就是在Activity中设置监听器:

window.addSystemUIVisibilityListener { systemUiVisible = it
}

To toggle the system UI, set a click listener to the View we want to use, for example:

要切换系统用户界面,请将点击侦听器设置为我们要使用的视图,例如:

camera_container.setOnClickListener {toggleSystemUi()
}

Finally, the Activity’s toggleSystemUi() method is very simple:

最后,Activity的toggleSystemUi()方法非常简单:

private fun toggleSystemUi() {
if (systemUiVisible) {
hideSystemUI()
} else {
showSystemUI()
}
}

The API for modifying the system UI can be confusing, especially after the old API has been deprecated and there are not many examples or enough documentation for the new Android 11’s API. Hopefully this article made it easier to understand.

用于修改系统UI的API可能会造成混乱,尤其是在不赞成使用旧API并且没有太多示例或足够新Android 11 API的文档之后。 希望本文使它更容易理解。

翻译自: https://medium.com/@cernilovsky/modifying-system-ui-visibility-in-android-11-e66a4128898b

android ui可见

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值