手势管理:处理手势冲突。第三部分

在启动高级基础的 Android开发课程之前,准备了本文的翻译



这是有关手势控制的系列文章中的第三篇。如果您错过第一部分第二部分的翻译内容,则可以熟悉一下它们。

在之前的文章中,我们讨论了从边缘到边缘填充屏幕空间的主题。在本文中,我们将研究如何处理应用程序的手势Android 10中的新系统手势之间发生的任何冲突

手势冲突是什么意思?让我们来看一个例子。假设我们有一个音乐播放器,允许用户通过拖放来滚动浏览当前歌曲SeekBar



不幸的是,它SeekBar太靠近返回主屏幕的手势区域,因此开始快速切换到上一个应用程序(QuickSwitch)的手势给用户带来了不便。

在手势区域所在的屏幕的任何边缘上都可能发生相同的情况。有许多可能导致冲突的示例,例如:导航抽屉DrawerLayout),轮播(ViewPager),滑块SeekBar),在列表中滑动

这就带来了一个问题:“我们如何解决这个问题?”为简化此问题,我们创建了一个流程图,可根据情况为您提供答案。


您可以在此处找到流程图的PDF版本

我希望这些问题不需要解释,但以防万一,我将逐一讨论这些问题:

1.应用程序是否需要隐藏导航栏和状态栏?


第一个问题是应用程序的主要用例是否需要隐藏导航和/或状态栏。通过隐藏,我们意味着这些系统面板根本不可见。并不意味着您已经在应用程序中实现了“边缘到边缘”或类似的概念。

对这个问题回答“是”的可能原因:


应对此问题回答“是”的应用程序的常见示例是游戏,视频播放器,照片查看器,绘图应用程序。

2.使用UI的主要场景涉及在手势区域内/周围滑动?


这个问题可以找出您的UI是否在用户必须滑动的手势区域(“后退”和“家”)中/旁边包含任何元素。

游戏通常会说“是”,原因是

  • 屏幕上的控件通常位于屏幕的左/右边缘和底部附近。
  • 在某些游戏中,您需要在屏幕上任何位置上的元素上滑动。

除游戏外,UI的常见示例包括:

  • UI裁剪照片,其中可拖动框架也在屏幕的左右边缘附近。
  • 用户可以在覆盖整个屏幕的画布上绘图的绘图应用程序。

3.手势区域中/周围经常使用的视图?


我希望这是一个相当简单的问题。这也包括视图,这些视图覆盖手势的区域,然后扩展到屏幕的很大一部分,例如DrawerLayout屏幕ViewPager

4.视图涉及滑动/拖动?


我们稍微改变了策略,开始研究个人观点。对于您对第三个问题持肯定态度的任何观点,我们作一点澄清:用户是否应该滑动/拖动它?

在许多示例中,您必须回答“是»:SeekBarsBottomSheet甚至是PopupMenu(必须拖动才能打开)。

5.视图是否完全/大致位于手势区域下?


基于第四个问题,我们现在阐明视图是完全还是主要位于手势区域。

如果您的视图位于这样的可滚动容器中RecyclerView,请以不同的方式思考此问题:完全/基本上扩展的视图是否在所有滚动位置的手势区域下都属于?如果用户可以从手势区域下方滚动视图,则您无需执行任何操作。

在上图中,您可能会注意到轮播全屏(ViewPager)作为否定答案的示例,并且想知道为什么不需要处理这种情况。这是由于以下事实:20dp与视图的宽度相比,左/右手势的区域在宽度上相对较小(默认值:每个)。典型纵向屏幕上手机屏幕的宽度约为360dp,留下了320dp的可用空间,用户没有困难(这几乎屏幕的90%)。即使具有内部边距/凹痕,用户仍可以舒适地滚动浏览转盘。

6.视图边框是否与任何必需的手势区域重叠?


最后一个问题阐明了视图是否在任何必需的手势区域下。如果您回想起上一篇文章,您会记得系统手势强制性区域是屏幕手势始终优先的屏幕区域。

在Android 10中,只有一个强制性手势区域位于屏幕底部,该区域允许用户返回家中或打开其最新应用程序。这可能会在平台的将来版本中发生变化,但是现在我们只需要使用屏幕底部的视图即可。

典型示例有:

  • 无模式BottomSheet,因为它们倾向于折叠成屏幕底部的一个小的拖放视图。
  • 屏幕底部的水平滚动轮播,例如带有贴纸的界面。

既然我们已经解决了问题,我们希望您能找到其中一种解决方案,因此让我们更详细地研究每个解决方案。

没有冲突要处理


让我们从最简单的“解决方案”开始,什么都不做

当然,也许仍有进行优化的空间(请参阅下面的部分),但是幸运的是,在打开手势导航模式的情况下使用该应用程序不会出现严重问题。

如果日程安排将您带到这里,但您仍然觉得有问题,请告诉我们也许我们错过了一些东西。

从手势区域移动视图


正如我们从上一篇文章中学到的那样,存在一些实例来告诉您的应用程序系统手势区域在屏幕上的位置。解决手势冲突的一种方法是从手势区域移动所有冲突的视图。这对于屏幕底部的视图尤为重要,因为该区域是强制手势区域,并且应用程序无法在此处使用手势排除API。

让我们再来看一个例子。我们上面显示了一个音乐播放器界面。它包含SeekBar位于屏幕底部的,允许用户滚动浏览歌曲。


屏幕底部带有SeekBar的UI音乐播放器

但是当用户尝试跳过歌曲时,会发生这种情况:


记录与SeekBar冲突的系统手势

这是因为手势的下部区域与SeekBar重叠,因此向后倾斜手势优先。这是手势区域的可视化:



一个简单的解决方案


这里最简单的解决方案是添加一个额外的缩进/边距,以使SeekBar从手势区域向上移动。这样的事情:



如果在此示例中拖动SeekBar,您将看到我们不再激活归位手势:


SeekBar不再与较低的系统手势冲突。

要实现此功能,我们需要使用API 29和Jetpack Core库 v1.2.0(当前为alpha)中可用的新系统手势插入在示例中,我们增加了底部缩进量以匹配较低手势inset的值SeekBar

ViewCompat.setOnApplyWindowInsetsListener(seekBar) { view, insets ->
     //      view ,    system gesture insets
    view.updatePadding(
        bottom = insets.systemGestureInsets.bottom
    )
    insets
}

如果您有兴趣学习如何使其更易于使用WindowInsets,可以阅读有关该主题的另一篇文章:

WindowInsets-侦听器布局

进一步行动


此时,您可以确定该工作已经完成,对于某些布局,这很可能是最终解决方案。但是在我们的示例中,用户界面在视觉上逐渐退缩,下方有很多丢失的空间SeekBar因此,除了简单地向上移动视图之外,我们还可以重新设计布局以避免丢失空间:


SeekBar移至回放面板的顶部。

在这里,我们移到SeekBar了回放面板的顶部,完全在手势区域之外。这意味着我们不再需要人为地增加面板的高度来容纳SeekBar

, . « ».

API


上一篇文章中,我们提到“应用程序可以排除屏幕某些部分的系统手势应用程序使用最早在Android 10中出现的手势排除API来执行此操作。

系统提供了两种不同的功能来排除手势区域:View.setSystemGestureExclusionRects()Window.setSystemGestureExclusionRects()。您应该使用什么取决于应用程序:如果您使用Android View,则系统首选View API,否则请使用该WindowAPI。

两种API的主要区别在于Window API期望任何矩形都在窗口的坐标空间中。如果使用视图,通常将改为在视图坐标空间中工作。View API负责坐标空间之间的转换,也就是说,您仅需要根据视图的内容进行推理。

让我们来看一个例子。我们将再次使用音乐播放器的示例,该示例SeekBar位于屏幕的整个宽度上。SeekBar在上一节中,我们用返回主屏幕的手势解决了冲突,但是仍然需要注意手势的左右两个区域。

让我们看看当“滑块”用户尝试跳过歌曲时会发生什么SeekBar(圆形拖动)位于边缘之一附近:


SeekBar与后手势区域冲突

由于滑块位于右手势区域下方,因此系统认为用户正在尝试使用该手势返回,因此显示了后箭头。对于用户而言,这很不方便,因为他们现在可能不想返回。我们可以使用上述手势排除API来解决此问题,以排除滑块的边框。

手势异常API通常在两个地方调用:onDraw()渲染视图时,onLayout()否则。您的观点通过了List<Rect>包含所有要排除的矩形。如前所述,这些矩形必须位于其自己的视图坐标系中。

通常,您会创建一个类似这样的函数,该函数将从onLayout()和/或调用onDraw()

private val gestureExclusionRects = mutableListOf<Rect>()
private fun updateGestureExclusion() {
   //   ,      Android 10+
   if (Build.VERSION.SDK_INT < 29) return
  // -,     
   gestureExclusionRects.clear()
      //   ,    .  SeekBar    .
   thumb?.also { t ->
       gestureExclusionRects += t.copyBounds()
   }
   //            view,       ,     
   // ,       
   systemGestureExclusionRects = gestureExclusionRects
}

一个完整的例子可以在这里找到

添加此内容后,可以按预期方式在边缘附近倒回:


SeekBar在后手势区域中工作

请注意上面的示例。SeekBar已经在Android 10中为您自动执行此操作,因此您无需自己执行此操作。在这里,我们只是作为示例向您展示总体轮廓。

局限性


尽管手势排除API似乎是解决所有手势冲突的完美解决方案,但实际上并非如此。使用手势排除API,您可以声明应用程序的手势比系统返回操作更为重要。这是一个强有力的声明,因此,当您无能为力时,此API旨在作为紧急出口。

使用手势排除API,您可以声明应用程序的手势比回溯的系统操作更重要。

由于API提供的行为可能会违反舒适的用户体验,因此系统限制了它的使用:应用程序每个边缘最多只能排除200dp

这是开发人员听到这些时遇到的一些常见问题:

为什么需要限制?我希望上面的解释已经使您有一个原因。我们认为,对于用户而言,始终如一地从侧面滑动回去非常重要。始终贯穿整个设备,而不是一个应用程序。这种限制似乎过于严格,但是仅一个应用程序(不包括屏幕的整个边缘)足以给用户带来不便,这将导致该应用程序的删除或更为彻底的操作。

换句话说,导航系统应始终保持一致且易于使用。

为什么是200dp?200dp的论点非常简单。正如我们之前提到的,手势排除API旨在作为最后的手段,因此此限制的计算方法是几个重要触摸目标的倍数。感觉目标的最小建议尺寸48dp.4感觉目标× 48dp = 192dp。添加更多的缩进,我们得到了价值200dp

如果我需要排除每个边缘200dp以上的像素怎么办?系统将仅排除您要求的最低200dp。


屏幕外“我的视图” 的底部边缘算起,系统允许请求总高度为200 dp,

这是否被视为限制?不,系统仅考虑屏幕内排除的矩形。同样,如果视图部分位于屏幕上,则仅考虑所请求矩形的可见部分。

深入了解下一篇文章


也许在阅读完这篇文章之后,您想知道为什么我们没有考虑流程图的右侧。以下解决方案专为使用整个屏幕进行渲染的应用程序而设计。我们将在下一部分中讨论它们。






All Articles