实现边到边的体验 | 让您的软键盘动起来 (一)
两个 Android 11 中软键盘动画效果的示例: Google Search 应用 (左),Messages (右)
Android 11 https://developer.android.google.cn/android11 WindowInsets https://developer.android.google.cn/reference/kotlin/android/view/WindowInsets Google Search https://play.google.com/store/apps/details?id=com.google.android.googlequicksearchbox&hl=en_GB Messages https://play.google.com/store/apps/details?id=com.google.android.apps.messaging
让我们来看看如何在您的应用中添加这种用户体验。总共分为三步:
首先,我们需要做到 "边到边" (edge-to-edge);
第二步,应用需要针对边衬区动画做出反应;
最后第三步就是应用在恰当的场景中控制并使用边衬区动画。
实现边到边 (edge-to-edge)
实现边到边跟软键盘有什么关系?
应用如何实现边到边?
如果我们回想去年的介绍,实现边到边可以分为三步:
改变系统栏的颜色
设置全屏布局
处理视觉冲突
view.systemUiVisibility =
// 通知系统,视窗希望在极端的情况下该如何布局内容。查看文档来获取更具体的信息。
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
// 通知系统,视窗希望在导航栏被隐藏的情况下如何布局内容。
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
systemUiVisibility https://developer.android.google.cn/reference/android/view/View.html#setSystemUiVisibility(int)
// 通知视窗,我们(应用)会处理任何系统视窗(而不是 decor)
window.setDecorFitsSystemWindows(false)
// 或者您可以使用 AndroidX v1.5.0-alpha02 中的 WindowCompat
WindowCompat.setDecorFitsSystemWindows(window, false)
WindowCompat https://developer.android.google.cn/reference/kotlin/androidx/core/view/WindowCompat
#3: 处理视觉冲突
ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
v.updatePadding(bottom = insets.systemWindowInsets.bottom)
// 返回边衬区,这样它们才能够继续在视图树中继续传递下去
insets
}
WindowInsets
https://developer.android.google.cn/reference/kotlin/android/view/WindowInsets
WindowInsetsCompat
https://developer.android.google.cn/reference/kotlin/androidx/core/view/WindowInsetsCompat
OnApplyWindowInsetsListener
https://developer.android.google.cn/reference/androidx/core/view/OnApplyWindowInsetsListener
系统视窗边衬区
https://developer.android.google.cn/reference/kotlin/androidx/core/view/WindowInsetsCompat#getsystemwindowinsets
ViewCompat.setOnApplyWindowInsetsListener(v) { view, windowInsets ->
val sysWindow = windowInsets.systemWindowInsets
val stable = windowInsets.stableInsets
val systemGestures = windowInsets.systemGestureInsets
val tappableElement = windowInsets.tappableElementInsets
}
和 systemUiVisibility API 类似,许多 WindowInsets API 已经被弃用了,取而代之的一些新函数来查询不同类型的边衬区:
getInsets(type: Int) 会返回指定类型的可见边衬区。
getInsetsIgnoringVisibility(type: Int) 会返回所有边衬区,无论它们是否可见。
isVisible(type: Int) 会返回 true 如果指定的类型是可见的。
systemUiVisibility API https://developer.android.google.cn/reference/android/view/View.html#setSystemUiVisibility(int) getInsets(type: Int) https://developer.android.google.cn/reference/android/view/WindowInsets#getInsets(int) getInsetsIgnoringVisibility(type: Int) https://developer.android.google.cn/reference/android/view/WindowInsets#getInsetsIgnoringVisibility(int) isVisible(type: Int) https://developer.android.google.cn/reference/android/view/WindowInsets#isVisible(int) WindowInsets.Type https://developer.android.google.cn/reference/kotlin/android/view/WindowInsets.Type AndroidX Core https://developer.android.google.cn/jetpack/androidx/releases/core 发行注记 https://developer.android.google.cn/jetpack/androidx/releases/core#1.5.0-alpha02
再来看如果我们用新的 API 来更新之前的示例,它们就变成:
ViewCompat.setOnApplyWindowInsetsListener(...) { view, insets ->
- val sysWindow = insets.systemWindowInsets
+ val sysWindow = insets.getInsets(Type.systemBars() or Type.ime())
- val stable = insets.stableInsets
+ val stable = insets.getInsetsIgnoringVisibility(Type.systemBars())
- val systemGestures = insets.systemGestureInsets
+ val systemGestures = insets.getInsets(Type.systemGestures())
- val tappableElement = insets.tappableElementInsets
+ val tappableElement = insets.getInsets(Type.tappableElement())
}
软键盘类型 ⌨️
在 Android 中如何查看软键盘的可见性?
https://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android
val insets = ViewCompat.getRootWindowInsets(view)
val imeVisible = insets.isVisible(Type.ime())
val imeHeight = insets.getInsets(Type.ime()).bottom
ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
val imeVisible = insets.isVisible(Type.ime())
val imeHeight = insets.getInsets(Type.ime()).bottom
}
软键盘类型 https://developer.android.google.cn/reference/android/view/WindowInsets.Type#ime() isVisible() https://developer.android.google.cn/reference/kotlin/android/view/WindowInsets#isvisible IME https://developer.android.google.cn/reference/kotlin/android/view/WindowInsets.Type#ime() OnApplyWindowInsetsListener https://developer.android.google.cn/reference/androidx/core/view/OnApplyWindowInsetsListener
隐藏或显示软键盘
如何关闭/隐藏 Android 软键盘? https://stackoverflow.com/questions/1109022/how-do-you-close-hide-the-android-soft-keyboard-using-java
val controller = view.windowInsetsController
// 显示软键盘( IME )
controller.show(Type.ime())
// 隐藏软键盘
controller.hide(Type.ime())
WindowInsetsController https://developer.android.google.cn/reference/android/view/WindowInsetsController show() https://developer.android.google.cn/reference/android/view/WindowInsetsController#show(int) hide() https://developer.android.google.cn/reference/android/view/WindowInsetsController#hide(int)
WindowInsetsController
之前我们提到过,有一些 View.SYSTEM_UI_* 标志已经在 Android 11 中被弃用,并且被新的 API 代替。还有一些 View.SYSTEM_UI 标志本来是被用来改变系统 UI 的外观和可见性的,包括:
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
View.SYSTEM_UI_FLAG_LOW_PROFILE
View.SYSTEM_UI_FLAG_FULLSCREEN
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
View.SYSTEM_UI_FLAG_IMMERSIVE
View.SYSTEM_UI_FLAG_VISIBLE
View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
WindowInsetsController https://developer.android.google.cn/reference/android/view/WindowInsetsController
沉浸模式
Markers 应用,展示隐藏系统 UI
val controller = view.windowInsetsController
// 当我们想隐藏系统栏
controller.hide(Type.systemBars())
// 当我们想显示系统栏
controller.show(Type.systemBars())
应用使用沉浸模式来让用户在系统栏隐藏的时候可以通过滑动来召回系统栏。为了实现这个效果,我们使用 WindowInsetsController 并且改变 setSystemBarsBehavior() 为 BEHAVIOR_SHOW_BARS_BY_SWIPE:
val controller = view.windowInsetsController
// 现在开始沉浸式..
controller.setSystemBarsBehavior(
WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE
)
// 当我们想要隐藏系统栏
controller.hide(Type.systemBars())
WindowInsetsController https://developer.android.google.cn/reference/android/view/WindowInsetsController 沉浸模式 https://developer.android.google.cn/training/system-ui/immersive#immersive setSystemBarsBehavior() https://developer.android.google.cn/reference/android/view/WindowInsetsController#setSystemBarsBehavior(int) BEHAVIOR_SHOW_BARS_BY_SWIPE https://developer.android.google.cn/reference/android/view/WindowInsetsController#BEHAVIOR_SHOW_BARS_BY_SWIPE
val controller = view.windowInsetsController
// 现在开始吸附式沉浸式体验 ...
controller.setSystemBarsBehavior(
BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
)
// 当我们想要隐藏系统栏
controller.hide(Type.systemBars())
BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE https://developer.android.google.cn/reference/android/view/WindowInsetsController#BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
状态栏内容的颜色
两个应用,左边的使用的是深色状态栏背景,右边的使用的是浅色背景
val controller = view.windowInsetsController
// 启用浅色状态栏内容
controller.setSystemBarsAppearance(
APPEARANCE_LIGHT_STATUS_BARS, // value
APPEARANCE_LIGHT_STATUS_BARS // mask
)
注意: 您也可以在主题中通过设置 android:windowLightStatusBar 实现上述效果。在您知道这个值不会变动的情况下,这个方式可能更好。
setSystemBarsAppearance()
https://developer.android.google.cn/reference/android/view/WindowInsetsController#setSystemBarsAppearance(int,%20int)
APPEARANCE_LIGHT_STATUS_BARS
https://developer.android.google.cn/reference/android/view/WindowInsetsController#APPEARANCE_LIGHT_STATUS_BARS
APPEARANCE_LIGHT_NAVIGATION_BARS
https://developer.android.google.cn/reference/android/view/WindowInsetsController#APPEARANCE_LIGHT_NAVIGATION_BARS
AndroidX 中的 WindowInsetsController?
实现边到边: ✔️
推荐阅读