[Android翻译]动画你的键盘(第1部分)

610 阅读8分钟

Kiran Puritwitter.com/_kiranpuri的插图

副标题:新的WindowInsets API用于检查键盘(IME)的可见性和大小。

原文地址:medium.com/androiddeve…

原文作者:medium.com/@chrisbanes

发布时间:2020年8月25日-7分钟阅读

Android 11中的新功能是让应用程序能够在屏幕键盘的打开和关闭之间创建无缝过渡,这一切都由Android 11中的WindowInsets APIs的大量改进所驱动。

下面是它在Android 11上运行的两个例子。它已经被集成到谷歌搜索应用,以及信息应用中。

Android 11中键盘动画的两个例子:谷歌搜索应用(左),信息(右)。

那么,让我们来看看如何在你的应用中添加这种体验。有三个步骤。

  1. 首先,我们需要去边缘到边缘。
  2. 第二步是由应用开始对插屏动画做出反应。
  3. 而第三步是由应用控制和驱动内嵌动画,如果它对你的应用有意义的话。

这些步骤中的每一个步骤都是相辅相成的,所以我们将在单独的博文中介绍每一个步骤。在这第一篇文章中,我们将介绍从边缘到边缘,以及Android 11中相关的API变化。

边缘到边缘

去年,我们提出了 "边缘到边缘 "的概念,以此作为应用程序充分利用Android 10中新的手势导航的一种方式。

手势导航。从边缘到边缘(一)

简单的总结一下,从边缘到边缘会导致你的应用画在系统栏后面,就像你在左边看到的那样。

引用我自己去年的一句话。

通过从边到边的方式,应用会被布置在系统栏的后面。这是为了让你的应用内容能够彰显出来,为用户创造更加沉浸式的体验。

那么,从边缘到边缘和键盘有什么关系呢?

实际上,从边缘到边缘不仅仅是在状态栏和导航栏后面画画。它是应用程序负责处理那些可能与应用程序重叠的系统UI。

两个明显的例子就是我们前面提到的状态栏和导航栏。然后,我们还有屏幕键盘,或者有时被称为IME;它只是另一块需要注意的系统UI。

应用如何去边缘化?

如果我们回顾一下去年的指导意见,去边缘化是由3个任务组成的。

  1. 改变系统栏的颜色
  2. 要求全屏布局
  3. 处理视觉冲突

我们要跳过第一个任务,因为自去年以来那里没有任何变化。步骤2和3的指导已经更新了Android 11的一些变化。让我们来看看。

#第2步:要求全屏布局。

第二步,应用程序需要使用systemUiVisibildeveloper.android.com/reference/a…ity API与一堆标志,来请求被全屏布局。

view.systemUiVisibility = 
    // Tells the system that the window wishes the content to
    // be laid out at the most extreme scenario. See the docs for
    // more information on the specifics
    View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
    // Tells the system that the window wishes the content to
    // be laid out as if the navigation bar was hidden
    View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION

如果你正在使用这个API并且已经更新了你的编译SDK版本到30,你会发现所有这些API现在都被废弃了。

它们已经被Window上的一个函数setDecorFitsSystemWindows()所取代。

// Tell the window that we (the app) want to handle/fit any system
// windows (and not the decor)
window.setDecorFitsSystemWindows(false)

// OR you can use WindowCompat from AndroidX v1.5.0-alpha02
WindowCompat.setDecorFitsSystemWindows(window, false)

取代了许多标志,你现在传入一个布尔值:如果应用程序想要处理任何系统窗口拟合(从而进入全屏),则为false

我们在WindowCompat中也有一个Jetpack版本的函数,最近在androidx.core v1.5.0-alpha02中发布。

所以这是更新的第2步。

#3:处理视觉冲突

现在我们来看看第三步:避免与系统UI重叠,可以总结为利用窗口insets来知道内容移动到哪里,避免与系统UI冲突。在Android上,insets由WindowInsets类来表示,AndroidX中的WindowInsetsCompat也是如此。

如果我们看看API 30更新之前的WindowInsets,最常用的inset类型是系统窗口inset。这些窗口覆盖了状态栏和导航栏,还有打开时的键盘。

要使用WindowInsets,你通常会在视图中添加一个OnApplyWindowInsetsListener,并处理任何传递给它的insets。

ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
    v.updatePadding(bottom = insets.systemWindowInsets.bottom)
    // Return the insets so that they keep going down the view hierarchy
    insets
}

在这里,我们正在获取系统窗口插页,然后更新视图的填充以匹配,这是一个非常常见的用例。

还有其他一些可用的inset类型,包括最近从Android 10中添加的手势inset。

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的大部分已经被废弃,取而代之的是新的函数来查询不同类型的insets。

我们刚才提到了很多 "类型"。这些在WindowInsets.Type类中被定义为函数,每个函数返回一个整数标志。你可以组合多个类型,使用位智OR来查询组合的类型,这一点我们一会儿就会看到。

所有这些API都已经被回传到AndroidX Core中的WindowInsetsCompat,所以你可以安全地使用它们回传到API 14(更多信息请参见发布说明)。

因此,如果我们回到之前的例子,将其更新到新的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())
}

IME的类型 ⌨️...

现在眼尖的👀中可能一直在看这个类型列表,并且一直在特别关注一种类型:IME类型

好了,我们终于可以回答这个StackOverflow的问题了,这个问题来自10多年前(时髦地晚了),关于如何检查键盘的可见性。🎉

如何在安卓系统中检查软件键盘的可见性?

要想得到当前键盘的可见性,我们可以获取根窗口的镶边,然后调用isVisible()函数,传入IME类型。

同样,如果我们想知道高度,也可以这样做。

val insets = ViewCompat.getRootWindowInsets(view)
val imeVisible = insets.isVisible(Type.ime())
val imeHeight = insets.getInsets(Type.ime()).bottom

如果我们需要监听键盘的变化,我们可以使用普通的OnApplyWindowInsetsListener,并使用同样的函数。

ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
    val imeVisible = insets.isVisible(Type.ime())
    val imeHeight = insets.getInsets(Type.ime()).bottom
}

隐藏/显示键盘

既然我们在回答StackOverflow的问题,那么这个11年前的问题呢,如何关闭键盘。

如何关闭/隐藏Android软键盘?

这里我们要介绍的是Android 11中另一个新的API,叫做WindowInsetsController

应用程序可以从任何视图中获得对控制器的访问,然后通过调用show()hide(),传递IME类型来显示或隐藏键盘。

val controller = view.windowInsetsController

// Show the keyboard (IME)
controller.show(Type.ime())

// Hide the keyboard
controller.hide(Type.ime())

但隐藏和显示键盘并不是控制器能做的全部... ...

WindowInsetsController

之前我们说过,在Android 11中,一些View.SYSTEM_UI_*标志已经被废弃,被新的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

与其他的类似,这些也已经在API 30中被废弃,被WindowInsetsController中的API所取代。

我们不打算对所有这些标志进行迁移,而是介绍一些常见的场景,看看如何更新它们。

沉浸式模式

这里你可以看到一个画图应用,它隐藏了系统UI,最大限度地增加了画图的空间。

标记应用,演示隐藏系统UI。

为了使用WindowInsetsController来实现,我们像之前一样使用hide()show()函数,但这次我们传入系统条类型。

val controller = view.windowInsetsController

// When we want to hide the system bars
controller.hide(Type.systemBars())

// When we want to show the system bars
controller.show(Type.systemBars())

该应用还使用了沉浸式模式,允许用户在隐藏系统条后刷回。为了使用WindowInsetsController实现这个功能,我们将隐藏和显示行为改为BEHAVIOR_SHOW_BARS_BY_SWIPE

val controller = view.windowInsetsController

// Immersive is now...
controller.setSystemBarsBehavior(
    WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE
)

// When we want to hide the system bars
controller.hide(Type.systemBars())

同样的,如果你使用的是粘性沉浸模式,我们也可以使用BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE来实现。

val controller = view.windowInsetsController

// Sticky Immersive is now ...
controller.setSystemBarsBehavior(
    BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
)

// When we want to hide the system bars
controller.hide(Type.systemBars())

状态栏内容颜色

下一个场景是围绕状态栏内容颜色。这里你看到两个应用。

两个应用程序,左边是使用深色的状态栏背景,右边是使用浅色的背景

左边的应用有一个深色的状态栏背景,有浅色的内容,比如时间和图标。但如果我们想要一个浅色的状态栏背景,像右边那样有深色的内容,我们也可以使用WindowInsetsController。 为此,我们可以使用setSystemBarsAppearance()函数,传递APPEARANCE_LIGHT_STATUS_BARS值。

val controller = view.windowInsetsController

// Enable light status bar content
controller.setSystemBarsAppearance(
    APPEARANCE_LIGHT_STATUS_BARS, // value
    APPEARANCE_LIGHT_STATUS_BARS // mask
)

如果你想设置一个深色的状态栏,就用0来代替,以清除该值。

注意:你可以在你的主题中通过设置android:windowLightStatusBar属性来实现这个功能。如果你知道这个值不会改变的话,这可能是比较好的。

同样,APPEARANCE_LIGHT_NAVIGATION_BARS标志也可以为导航条提供同样的功能。

AndroidX中的WindowInsetsController?

不幸的是,这个API的Jetpack版本还不存在,但我们正在努力。请继续关注。

边缘到边缘。✔️

那么第一步就完成了。在下一篇博文中,我们将研究第二步:应用程序对插入动画的反应。

给键盘做动画:对插入式动画的反应


通过www.DeepL.com/Translator(免费版)翻译