手势管理:处理视觉覆盖。 第二部分

期待着Android开发高级课程的开始,我们将继续与您分享一系列有用的翻译。





您正在阅读有关手势管理的系列文章中的第二篇。 您可以在这里找到第一部分。

在本系列的第一部分中,我们学习了如何在屏幕的“边缘到边缘”放置您的应用程序。 不幸的是,这种显示方式可能会导致这样的事实,即某些视图的位置将超出系统面板区域的边界,从而对用户隐藏起来。 在本文中,我们将弄清楚如何以不破坏系统面板操作的方式添加view

在本文中,我将其称为“ UI系统”。 这是位于屏幕上的任何系统界面的名称,无论是导航栏还是状态栏。 它还包括一个通知栏。

插图


通常, 插入一词会引起Android开发人员的恐惧,因为在Android Lollipop时代的某个时候,他们总是试图使用状态栏的工作区。 例如,关于StackOverflow的这个老问题有很多视图。

插图显示屏幕的哪些部分与系统界面(例如,导航栏或状态栏)相交。 交叉口可能只是意味着在内容顶部显示,但是在这里您还可以获取有关系统手势的信息。 我们可以使用inset尝试解决任何冲突,例如,通过将view从边缘移开。

在Android中, 插图WindowInsets类表示,而在AndroidX中由WindowInsetsCompat类表示。 从Android Q开始,创建应用程序屏幕时可以使用5种类型的插图 。 使用哪种类型的inset取决于具体情况,因此让我们分别查看每种类型并查看。

系统窗口插图


方法getSystemWindowInsets()

系统窗口插入是当今最常见的inset类型。 它们与API 1一起以各种形式存在,并且每当系统UI显示在应用程序顶部(沿z轴)时,它们就会出现在view层次结构中。 常见的示例是状态栏和导航栏,有时还包括屏幕键盘(IME)。

让我们看一个必须使用系统窗口inset的示例。 我们已经有一个FloatingActionButton(FAB) ,它位于屏幕的下角,其缩进量为16dp (根据指南 )。


Google I / O应用中的FAB转换为边缘到边缘之前

在遵循上一篇文章的步骤1和2之后,我们的view将位于导航栏的后面:


伸展到全屏后,Google I / O应用中的FAB

现在,您将看到会议日程安排位于导航栏的后面,这正是我们的目标-创建更加身临其境的体验。 有关如何使用列表/网格的更多详细信息,我们将在以后考虑。

让我们回到示例。 现在您看到FAB已隐藏,这反过来意味着用户将无法单击它。 我们要避免的是这种显示冲突。 由于面板位于较高位置,因此在使用按钮导航时,该示例看起来更加清晰(如图所示)。 在具有动态颜色适应功能的手势导航中,这可以工作,但是请记住,系统可以随时切换到半透明的稀松布 ,这可能会破坏交互体验。
现在是个好时机,告诉您在所有导航模式下测试应用程序的重要性。

那么,我们如何处理这种视觉冲突呢? 此时, 系统窗口插图开始起作用。 它们会告诉您系统面板在视图层次结构中的位置,您可以使用这些值将视图移离系统面板。

在上面的示例中,FAB位于右下角附近,因此我们可以使用systemWindowInsets.bottomsystemWindowInsets.right的值来增加每侧视图的缩进量,以便将其进一步移离导航栏。

完成此操作后,我们将获得以下信息:


稍后我们将讨论如何实现这一点。

TL DR: 系统窗口插图最适合移动/缩进系统面板不应该覆盖的交互式views

可贴图元素插图


方法getTappableElementInsets()

在列表的最下方,我们有可轻敲的元素插图 ,它们刚出现在Android Q中。它们与上面的系统窗口插图非常相似,但是它们对导航栏的可见性有所响应。

TL; DR:与可tappable element insets :您通常可以忽略它们,而使用system window insets 。 您可以跳到下面的“ 手势插入” 部分 ,或继续阅读。

可插拔元素插图定义了在交互式(可插拔)视图中需要应用的最小插图 。 在这种情况下,“最小”表示应用的值仍可能导致与系统面板发生冲突。 这就是它们与系统窗口插入区分开来的原因, 系统窗口插入区始终旨在避免与系统面板发生冲突。

让我们将示例与FloatingActionButton一起使用以显示值的差异:


导航栏的边框显示为粉红色。 绿色-FAB从底部边缘开始有特定的凹痕。



请记住,您永远都不能对上表中的值进行硬编码,因为可以调整导航栏的大小。 使用插图来避免冲突。

我们可能已经注意到,当设备处于按钮导航模式时,可tappable element insetssystem gesture insets行为相同。 当设备使用手势控制并且打开了动态颜色自适应功能时,按键区别就变得可见。 在这种情况下,导航栏是透明的,这意味着从理论上讲可以在其中布置交互式views ,因此从底部开始的缩进为0。

尽管事实并不知道insets应该位于何处,所以在理论上使用可点击元素插图可以得到类似的结果:



由于view非常靠近导航栏,因此效果并不理想,这将给用户带来不便。

实际上,使用系统窗口 嵌入可以更好地处理几乎所有可点击元素嵌入的用例。

手势插图


方法getSystemGestureInsets()getMandatorySystemGestureInsets()

我们将要查看的下一种类型的insets是在Android Q版本中添加的gesture insets 。我记得Android Q引入了一种新的手势控制模式,该模式允许用户使用两个触摸手势来控制设备,该操作可以如下执行:

  1. 从显示屏的边缘之一水平滑动。 这将触发返回动作。
  2. 从显示屏的底部边缘向上滑动。 这将允许用户转到其主屏幕或上次使用的应用程序。


Android Q中的手势演示

系统手势插入反映了系统手势优先于应用程序中触摸手势的窗口区域。 您可能已经注意到,我已经在上面指出了两种方法。 这是由于这样的事实,实际上有两种类型的系统手势插入 :一种存储所有手势区域,另一种是包含强制性系统手势插入的子集。

系统手势插入


首先,我们有系统手势插图 。 它们包含屏幕手势中系统手势优先于应用程序手势的所有区域。 在Android Q中,这意味着插图将看起来像这样,即,包含从底部边缘开始的缩进以返回主屏幕的手势,在左侧和右侧包含缩进的反向手势:

  0 +--------------+ | | | System | 40 | Gesture | 40 | Insets | | | +--------------+ 60 

系统手势插入何时会派上用场? 这些insets指示系统手势优先的位置,因此您可以使用它们来主动移动需要滑动手势的任何视图。

示例包括从下方延伸屏幕 ,在游戏中滑动,轮播(例如ViewPager )。 通常,您可以使用这些insets从屏幕边缘移动/缩进。

强制系统手势插入


强制系统手势插入系统手势插入的子集,并且仅包含无法从应用程序中删除的区域(例如,名称)。 在下一篇文章的主题中,我们向前看了一些,我们将讨论处理手势冲突,但是为了理解当前的文章,只知道应用程序可以从屏幕的某些区域删除系统手势。

强制性系统手势插入指示系统手势始终优先且必须的屏幕区域。 在Android Q上,当前唯一的强制性区域是屏幕底部的主屏幕手势区域。 这是必需的,以便用户始终可以退出应用程序。
以Android Q设备上的手势插入为例,您将看到以下内容:

  0 0 +--------------+ +--------------+ | | | Mandatory | | System | | System | 40 | Gesture | 40 0 | Gesture | 0 | Insets | | Insets | | | | | +--------------+ +--------------+ 60 60 

可以看出, 系统手势插入在左侧,右侧和底部都包含缩进,而必要的插入仅包含底部的缩进,因此返回主屏幕的手势可以正常工作。 在下一篇文章中,我们将讨论有关删除手势区域的更多信息。

稳定的插图


方法getStableInsets()

稳定的插图是Android上可用的最后一种插图 。 它们对于管理手势并不是特别重要,但是我认为值得一谈。

稳定的插入是指系统窗口的插入 ,但它们指示系统界面可以在应用程序顶部显示的位置,而不是原则上显示该位置的位置。 稳定的插入主要用于以下情况:配置系统接口,以便可以打开或关闭其可见性,例如,在使用倾斜沉浸式模式(例如,游戏,查看照片和视频播放器)时。

处理插图


希望您对各种类型的inset是什么有更好的了解,所以现在让我们看看如何在应用程序中使用它们。

访问WindowInsets的主要方法是setOnApplyWindowInsetsListener方法。 让我们来看一个示例view ,我们希望将其缩进,以使其不会出现在导航栏的后面:

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

在这里,我们仅将view的底部缩进设置为系统窗口inset的底部缩进值。

注意 :如果在ViewGroup上执行此操作,则可能要设置android:clipToPadding="false" 。 这是由于所有默认情况下都缩进了所有类型的剪贴画 。 此属性通常与RecyclerView一起使用 ,我们将在下一篇文章中对其进行详细讨论。

确保您的收听功能是幂等的。 如果多次调用相同的插入,则每次的结果应相同。 下面给出了一个非幂等函数的示例:

 ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets -> v.updatePadding(bottom = v.paddingBottom + insets.systemWindowInsets.bottom) insets } 

每次调用listen函数时,它的缩进都不应增加(ala + =)。 在视图层次结构的生命周期中,窗口插入可以随时发生,也可以多次发生。

喷气背包


我建议牢记的另一件事是使用JetpackWindowInsetsCompat类,而不管您的最低SDK版本如何。 多年来,WindowInsets API已得到改进和扩展,并且兼容版本提供了所有API级别的API一致性和行为。

在Android Q中可用的新插入类型受到影响的地方compat方法是一组在所有API级别上对主机设备均有效的值。 要访问Android X中的新API 请将其升级到androidx.core:core:1.2.0-xxx (现在为alpha版本)或更高版本。 在这里您可以找到最新版本。

让我们走得更远


我上面提到的方法是使用WindowInsets [Compat] API的最简单方法,但是它们会使您的代码变得太长和重复。 早些时候,我写了一篇文章,其中详细描述了可以大大提高使用适配器活页夹处理窗口插图的人体工程学的方法。 您可以在这里阅读。

在下一篇文章中,我们将学习如何处理应用程序的手势与系统手势之间可能发生的任何冲突。

仅此而已。 我们在免费的网络研讨会中等待所有人,我们的专家将在其中详细讨论该课程

Source: https://habr.com/ru/post/zh-CN480778/


All Articles