借助 WindowInsetsCompat
,您的应用可以查询和控制屏幕键盘(也称为“IME”),这与它与系统栏的交互方式类似。您的应用还可以使用 WindowInsetsAnimationCompat
在软件键盘打开或关闭时实现无缝过渡。
前提条件
在设置软件键盘的控件和动画之前,请将应用配置为无边框显示。这样,它可以处理系统窗口边衬区,例如系统栏和屏幕键盘。
检查键盘软件可见性
使用 WindowInsets
检查软件键盘的可见性。
Kotlin
val insets = ViewCompat.getRootWindowInsets(view) ?: return val imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime()) val imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
Java
WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(view); boolean imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime()); int imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom;
或者,您也可以使用 ViewCompat.setOnApplyWindowInsetsListener
观察软件键盘可见性的变化。
Kotlin
ViewCompat.setOnApplyWindowInsetsListener(view) { _, insets -> val imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime()) val imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom insets }
Java
ViewCompat.setOnApplyWindowInsetsListener(view, (v, insets) -> { boolean imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime()); int imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom; return insets; });
将动画与软件键盘同步
用户点按文本输入字段会使键盘从屏幕底部滑动到位,如以下示例所示:
图 2 中标记为“UnSYNCd”的示例显示了 Android 10(API 级别 29)中的默认行为,其中应用的文本字段和内容会卡入到位,而不是与键盘的动画同步,这种行为可能在视觉上很不协调。
在 Android 11(API 级别 30)及更高版本中,您可以使用
WindowInsetsAnimationCompat
在键盘从屏幕底部上下滑动时同步应用的过渡。这样看起来更顺畅,如图 2 中标记为“已同步”的示例所示。
使用要与键盘动画同步的视图配置 WindowInsetsAnimationCompat.Callback
。
Kotlin
ViewCompat.setWindowInsetsAnimationCallback( view, object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP) { // Override methods. } )
Java
ViewCompat.setWindowInsetsAnimationCallback( view, new WindowInsetsAnimationCompat.Callback( WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP ) { // Override methods. });
在 WindowInsetsAnimationCompat.Callback
中可以替换多种方法,即 onPrepare()
、onStart()
、onProgress()
和 onEnd()
。先调用 onPrepare()
,然后再进行任何布局更改。
在边衬区动画开始播放时以及由于动画而重新布局视图之前,系统会调用 onPrepare
。您可以使用它保存起始状态,在本例中为视图的底部坐标。
以下代码段展示了对 onPrepare
的调用示例:
Kotlin
var startBottom = 0f override fun onPrepare( animation: WindowInsetsAnimationCompat ) { startBottom = view.bottom.toFloat() }
Java
float startBottom; @Override public void onPrepare( @NonNull WindowInsetsAnimationCompat animation ) { startBottom = view.getBottom(); }
当边衬区动画开始时,系统会调用 onStart
。您可以使用它将所有视图属性设置为布局更改的最终状态。如果您将 OnApplyWindowInsetsListener
回调设置为任何视图,则此时已调用该回调。此时非常适合保存视图属性的最终状态。
以下代码段展示了对 onStart
的调用示例:
Kotlin
var endBottom = 0f override fun onStart( animation: WindowInsetsAnimationCompat, bounds: WindowInsetsAnimationCompat.BoundsCompat ): WindowInsetsAnimationCompat.BoundsCompat { // Record the position of the view after the IME transition. endBottom = view.bottom.toFloat() return bounds }
Java
float endBottom; @NonNull @Override public WindowInsetsAnimationCompat.BoundsCompat onStart( @NonNull WindowInsetsAnimationCompat animation, @NonNull WindowInsetsAnimationCompat.BoundsCompat bounds ) { endBottom = view.getBottom(); return bounds; }
当边衬区在运行动画过程中发生变化时,系统会调用 onProgress
,以便您可以替换边衬区并在键盘动画期间在每一帧时收到通知。更新视图属性,以使视图与键盘同步添加动画效果。
此时,所有布局更改都已完成。例如,如果您使用 View.translationY
移动视图,则每次调用此方法时,该值都会逐渐减小,并最终达到 0
到原始布局位置。
以下代码段展示了对 onProgress
的调用示例:
Kotlin
override fun onProgress( insets: WindowInsetsCompat, runningAnimations: MutableList<WindowInsetsAnimationCompat> ): WindowInsetsCompat { // Find an IME animation. val imeAnimation = runningAnimations.find { it.typeMask and WindowInsetsCompat.Type.ime() != 0 } ?: return insets // Offset the view based on the interpolated fraction of the IME animation. view.translationY = (startBottom - endBottom) * (1 - imeAnimation.interpolatedFraction) return insets }
Java
@NonNull @Override public WindowInsetsCompat onProgress( @NonNull WindowInsetsCompat insets, @NonNull List<WindowInsetsAnimationCompat> runningAnimations ) { // Find an IME animation. WindowInsetsAnimationCompat imeAnimation = null; for (WindowInsetsAnimationCompat animation : runningAnimations) { if ((animation.getTypeMask() & WindowInsetsCompat.Type.ime()) != 0) { imeAnimation = animation; break; } } if (imeAnimation != null) { // Offset the view based on the interpolated fraction of the IME animation. view.setTranslationY((startBottom - endBottom) * (1 - imeAnimation.getInterpolatedFraction())); } return insets; }
(可选)您可以替换 onEnd
。此方法在动画播放完毕后调用。这时非常适合清除所有临时更改。
其他资源
- GitHub 上的 WindowInsetsAnimation。