Android Q Labs | Android Q 手势导航

4,027 阅读20分钟

接下来我将为大家介绍一个新的系统UI的功能:手势导航。同时想要利用这个机会和大家聊一聊作为开发者,应该如何去适配全面屏或者说新的全手势导航的模式。

手持设备的演化史

随着时间的发展,我们可以看到手持设备的体积变得越来越小,屏幕本身则变得越来越大,然后屏幕在设备上的占有比例也变得越来越高。可以看到到最近年这些手持设备硬件上面的边框真的是变得越来越来越细小。同时,我们也看到很多硬件厂商在这方面有更多的创新,例如说可折叠式的手机。

全面屏的趋势

1.用户对全面屏的需求

2.各设备厂商的创新

2.应用对不同手势的支持

3.Android 系统的解决方案

这样一个全面屏的趋势,主要是由于用户对更大屏幕的需求。当然,这也离不开各个设备厂商在硬件方面各种各样的创新。

我们也看到,这两年除了在硬件方面之外,很多的厂商在软件方面去考虑如何使用全手势的这种操作来给用户更多的屏幕空间。不同的设备厂商提供了不同的手势操作。

那么对于在座的开发者来说,这虽然是一个好事情,但也可能意味着我们的工作量变大了:因为我们在研发过程中要适配不同的手势,可能要专门为一些设备的手势进行设计、研发、测试等等。

那么,从安卓系统角度,我们就想要去帮大家解决这个问题,所以在今年的 Android Q 我们为大家推出了新的手势导航的功能。

Android Q 全新的手势导航

你可能还是看到的是之前比较经典的三个按键的导航,那么你只要进入系统设置,里面有一个 gesture 选项,然后你就可以开启现在全新的手势导航了。

对于还没有机会去尝试新手势的朋友,我这边有三张动图,基本上体现了新的手势导航用户是如何来操作的。

可能应用演练的第一大特点就是我们原来底部的导航栏的黑色区域消失了,取而代之的是非常小的一个虚拟的导航按钮。

我们先从最左边的来讲,可以看到当用户从屏幕的底端,也就是现在虚拟的导航按键这里往上推的时候,用户可以退出当前应用,也就是说这个手势代替的是我们之前的home按键,即退出当前应用。

中间这个手势代替的是“用户回到最已经浏览过的应用”手势,操作是从底部往上推,然后停留。

最右边是今年新增加的另外一个手势,也就是返回的手势。他代替的是之前三个导航按键当中“返回”的按钮。

这张动图没有完全包括新的手势。在 Beta3 中新的手势是支持同时向左侧滑动和向右侧滑动这两个手势的。可以看到,这张动图中我们只包括了从左侧滑动。当用户在屏幕的边缘向左侧或者是向右侧滑动的时候,用户就可以回到上一个页面了。

说到这里可能大家会有一个疑问,因为在过去安卓系统上经典的三个按键的导航模式推出了很多年,大部分的用户也很习惯于使用这样的导航模式,那今年推出的新的全手势的导航,对于安卓微生态来说意味着什么呢?

我要先解释、澄清一下:首先三个按键的导航模式我们会继续沿用的

几个方面的原因,很重要的一点就是很多的用户已经习惯于这样的导航模式,特别是针对于有无障碍需求的一些用户,对他来说三个按键导航可以帮助他去使用设备,所以我们会继续沿用三个按键的导航。同时,我们会要求所有的安卓设备都继续支持三个按键的导航,用户就永远有一个可以返回去的备选方案。

在这个基础上有今年我们推出的全手势导航,具体的手势内容就是主要的那三种手势,也就是刚刚大家看到动图。接下来我们会继续和各个厂商努力把手势导航的这几个操作统一化,以便帮助开发者去处理一个统一的手势导航。那么到这里 Android Q 中新的手势导航功能本身我就介绍到这里。

如何面对全面屏趋势?

接下来我想要跟大家聊一聊,作为开发者如何去面对现在全面屏的趋势,或者说新的手势导航,给大家几点建议。

1.建议大家利用全面屏

既然我们有了更大的屏幕,作为开发者,我们希望大家去用这样一个更大的屏幕。当我们把原来的布局放在新的全面屏的时候,可能就会遇到一些跟系统UI有冲突的地方。

2.利用 insets 调整布局

这个时候,我们建议大家利用 insets 这个类来调整你的布局。

3.利用手势操作带来的冲突

如果说你的应用内部有一些功能,或者说有一些手势恰好是和系统的新手势重合或者冲突(大部分的情况下应该不会有这样的冲突发生,我之后会给大家一个例子),如果有这样的情况,大家务必要把这些问题解决一下。

什么是全面屏

说了这么久的全面屏,全面屏到底指的是什么?

这张图片基本上指的是过去这么长时间,一般应用可以利用的在屏幕上的一个所谓的“安全范围”。在 Android Q 上,我们首先建议大家去考虑使用最下面的导航栏区域。

导航栏不光包括我给大家展示的新的手势导航,同时也包括了三个按钮的经典式的导航,同样会推荐大家去使用导航栏的区域,之后会给大家介绍可以使用这个区域的 API,这个 API 是在旧的安卓版本上同样兼容的,所以建议大家在适配 Q 的时候也可以考虑是否在之前的版本上同样加入这样的支持。

另一方面,如果你的应用像这样:

你有一张图片或者是可以被用户滑动的内容,是可以利用到我们最上面的状态栏的。

那么在 Android Q 当中我们也建议大家去使用这样的状态栏,目的很简单:给用户全面屏的感受,沉浸式的感受。

同样,我们也建议大家在适配 Q 的时候考虑是否在之前的版本上同样加入这样的支持。那么我们就可以看到,我们现在应用所用的屏幕的范围,从之前的范围变成了如图这样一个全面屏的范围。

代码实现

那么我们具体应该怎样来实现?基本上也是三个部分:

你的应用需要告诉系统,你要在全面屏上布置显示的应用内容,在全面屏上显示的时候可能会出现一些问题。

例如说我们这里看到应用内容的最下面的部分,如果没有做任何优化的话,系统的导航栏可能就会盖住应用的内容。这个时候,作为开发者就需要对导航栏进行一些优化。 或者是我的应用内部有一些 view 或者是像上图中的 floatingActionButton (悬浮按钮)。如果说你原来开发的时候它的位置是写死的,那么当你在全面屏布置的时候,可能就会有一些跟系统 UI 的冲突,然后用户就没有办法去继续使用你之前的一些功能了。

那么我们来看第一部分,在全面屏上显示。

大家需要使用一个 API(不是新的API):setSystemUiVisibility。 这个 API 的名字很很清晰易懂,你可以用它来设置系统 UI 的可见度或是显示程度。那么当你使用那么当你在使用这个方法的时候,你要传入一些值,这些值告诉了系统如何去显示系统的 UI,或者说如何去显示我的应用的 UI。

setSystemUiVisibility

我把这些参数大部分列在了这里,这不是所有的参数。

通过传这个参数更改的是系统 UI 的可见度,也就是这个这个方法本身它的作用。

举几个例子:最上面 SYSTEM_UL_FLAG_LOW_PROGFILE,就是要让系统的 UI 呈现于一个 low profile 的状态,也就是一个低调的状态,不要影响到我的应用内容,那么接下来,如果我传入的是 SYSTEM_UL_FLAG_NAVIGATION,其实我就是告诉了系统,我要把导航栏隐藏起来。所以第一个部分是要告诉系统 UI 如何显示。

中间这部分的参数是特别适用于一个视频或者是游戏的界面想要给用户一种沉浸式的体验,那么就可以传入 SYSTEM_UL_FLAG_IMMERSIVE 这两个的参数,当用户需要看到这个系统 UI 按钮的时候,通过点击屏幕等等可以再让这个系统 UI 出现。

今天我要说的其实是最下面的这三个参数,通过使用这三个参数你就可以让你的应用在全面屏上显示。我们具体来看一下这三个参数:

首先这里有一个 view ,我对 view 又设置了这个 view 呈现时候的系统 UI 的可见度,那么我传入的第一个参数就是 SYSTEM_UL_FLAG_LAYOUT_HIDE_NAVIGATION,它主要指的就是我要把我的应用 layout 在屏幕上,当系统的 navigation,也就是导航栏消失的时候,我告诉了系统我要利用导航栏的区域来显示我的应用内容,那么下面这个参数是大部分时间我建议大家使用的,传入 SYSTEM_UL_FLAG_LAYOUT_STABLE,它指的是无论这个屏幕是怎样布局内容,它的布局比例和内容都不会发生变化,所以是呈现一个 stable 的状态的。

那么,针对于状态栏,如果你需要使用的话,你就要传入这样一个参数:STSTEM_UL_FLAG_LAYOUT_FULLSCREEN,这个时候就是告诉了系统,我要在全屏显示包括状态栏。

我们按照刚才的给出的建议,我有一个示例应用
可以看到我这个应用当中有一个底部应用内部的导航,那么我现在想要我的应用在全面屏上显示(在这里我只显示了应用的下半部分,但是对于状态栏其实是一样的),我们首先要告诉系统,我要在全屏显示,所以我在 view 上就设置了系统 UI 的可见度,传入了 stable 和 hide navigation 这两个参数。接着我就发现了一个问题:系统的 UI 栏盖住了我的内容
所以我需要告诉系统,我要把系统的导航栏设置为透明色,这个时候就可以在的 style 主题 theme 里面把 navigationBarColor 设置成 transparent,这样你的内容就可以全屏显示,并且让系统的导航栏处于一个透明的状态。

这个参数属性是我们在 loli pop 之后就已经推出的,并且建议大家在适配或者是针对于 Android 9.0或9.0以前的设备都应用把导航栏设置为透明的这样的一行代码。

为什么说是 Android9.0或者是9.0以前?因为在 Q 上我们推荐大家去利用全面屏,所以系统也要想要帮助大家少做一些事情,所以当你在 Android Q 上使用全面屏的时候,系统的导航栏会帮助你自动重新给导航栏着色,而且是动态变化的。稍微想要提一下的是,对于大部分的设备我们都有这样一个优化,但是对于某一些机型可能没有办法做到这样子的优化,对于那种情况,我们会给他一个 static 的一个颜色。但是在 Android Q 之前我们就没有办法帮大家,所以建议大家在 Android Q 之前,当你要使用导航栏的时候,你需要写这样一个代码,告诉系统说我要把导航栏设置为 transparent 透明色。

按照刚才的这两个例子,我先是把我的应用在全屏上显示,同时把导航栏设置为透明,然后就得到了这样的结果,但显然这不是我们想要的,应用内部的导航和系统的导航完全重合了,而且应用内部的导航是我要点击的操作,和系统的点击操作是完全冲突的。这个时候,如果有些处理过全屏幕或者是在全屏幕展现自己应用的朋友,可能用过 WindowInsets。

Insets 本身指的是添入物或者是加入的一个范围。那么在我们现在这个情境下,大家可以把它理解成是系统告诉你我的 UI 所在的位置区域,或者说是我的系统手势即将出现的位置。你可以通过这个值来移动应用内部的 view。让我的 view 和系统的 UI 不产生任何的冲突。

先来看第一类:WindowInsets 类,这个是我们从 API20 就已经推出的。可以看到在下面这张图当中,导航栏式系统的 UI 部分,也就是绿色的那个部分,当应用去全屏写的时候,FloatingActionButton 被导航栏遮住了,那么这个时候我需要获取系统 UI 的位置,我可以用 getsystemwindowsinsets 这个值来移动我的按钮,从而避免任何和系统意外的冲突。

刚才有提到,在 Android Q上有新的手势导航。既然有了新的手势导航,也有了新的系统手势所在的区域。

最下面蓝色的就是曾经的 window insight 是系统 UI 的区域,三个绿色的部分就是今年新的手势导航所占的 insight 区域,左右两边是”返回“手势。最下面的 gestureinsets 就是退出或者是回到“最近使用过的应用”手势。

可以看到下面的手势区域是比之前的导航栏的区域要宽的,因为用户在想要退出的时候,他可能需要操作的空间更大。

在 Android Q 上面我们也为大家提供了这样一个 API:getsystemGestureinsets,来帮助大家去获得现在新的手势导航所在的 insets 位置。

之前举的那个例子,FloatingActionButton 的问题,如果说你的应用和手势导航有一些冲突的,应该如何去解决呢?这里给大家分享一个例子,如果说应用是对图片处理的,例如说我看到这张图片我需要对它进行裁剪,那么任何一个用户看到这个界面,它自然会要采取的一个动作,就是从这四个红点当中去拖拽这张图片,从而进行裁剪。

一个不好的消息就是在 Android Q 上面,当用户去做这件事的时候,如果你没有进行适配,那么当他去拖拽,系统手势会有优先级。因为系统不知道你要用这些位置,那么怎么办?显然,当用户看到这个界面,他是会去处理这张图片的,于是我们就给大家带来这样一个 API:setsystemGestureExclutionRects 传入的参数是一个 list,就是一系列的。rectangle 就是长方形,也就是我们这里看到的那四个红色的点,因为在用户去拖拽那些区域的时候,用户是想要进行应用内部的手势操作,而不是开启系统本身的手势操作。为什么只有红色的区域需要去覆盖?答案是肯定的,因为如果拖拽出那个区域之后,系统手势导航只是在最边临界的位置,那么你用 API 的时候来获得系统的手势在哪里,针对性的把那个区域加入这样一个被覆盖的列表当中。这个方法是支持于大家使用返回手势的,这里有写退出应用的手势除外,因为在新的全手势导航下,用户离开应用,唯一的途径就是从最下面的退出手势,如果把这个手势覆盖了,用户就永远无法离开应用了。所以最下面的区域是强制不能被去除的。

如果你要告诉系统你要覆盖哪些范围,可以调用 setsystemGestureExclutionRects,如果你需要一帧一帧动态的去覆盖的话,你也可以把这个方法在 undraw 当中调用。

我在这里稍微停顿一下,因为之前听到有人说了这样一个方法:是不是说我的适配工作变得很容易,只要我应用内部的一些区域,我认为用户已经习惯这样用了,所以我去把所有的系统新的手势都覆盖掉。

我们的建议是尽量不要这样做,除非在有必要的情况下。因为很简单,如果大家有使用过新的手势操作,你会快注意到,其实返回这个动作是用户一天当中使用最多的一个动作,所以用户会非常快的习惯于新的手势操作,那么当它进入你的应用,突然间他习惯的操作的动作被打断了,这肯定不是一个很好的体验,我们还是最大程度 上想给用户一个非常一致的体验。

那么什么情况下你要用这个方法呢?就是我刚才举的那个例子,因为大家去修改图片这个例子,很明显的,任何用户当他处理这张图片的时候,他不会看到那些那四个位置,他不会想到要退出,而是很明显从 UI 上你给了用户一些提示:你现在要做的是我应用内部的一个操作,而不是说我开启系统的手势。所以希望大家在做这些决定的时候,也思考一下用户会从哪个方向来看待。

之前说到了有强制的手势区域,这边就给大家提供了这样一个 API 来获得被强制的手势范围,你可以调用 getMandatoryGestureInsets 方法来获得系统上哪一些区域是被系统强制的,也就是说你没有办法去覆盖它,给你自己应用有更高的一个优先级,在 Android Q 当中被强制的手势就是在最下面的退出应用区域。

稍微总结一下,getsystemwindowinsets 是我们已经有了一段时间的 API,这个 API 返回的是现在系统的 UI 所在的位置,或者是说将来会出现的位置。当你在用有某些模式可能现在系统 UI 消失了,但用户可能会重新唤醒它。那么,通过使用 getsystemwindowinsets 你就可以获得系统 UI 的位置。

什么时候使用它呢?

特别是当你有一些 view 是需要点击的时候。因为你覆盖了系统的 UI,系统 UI 是要点击的,所以当你使用 getsystemwindowinsets 的时候,你是要处理需要点击的 view 的。如果说你有 view 是需要拖动的,你就可以使用 getsystemgestureinsets,返回的是 Q 当中新加入的手势导航的区域,这个手势导航的区域,用户的使用方法是拖拽。所以,如果你有 view 是拖拽的话,你需要用这个方法来获取系统手势的范围,从而进行处理,最后就是系统告诉大家,你不能去覆盖的这样一个区域,也就是说你可以调用 getMandatoryGestureInsets 来获取你没有办法覆盖的区域。

常见情境

DrawLayout 优化

之前看到很多的朋友已经开始 Android Q 了,你可能会发现新的手势导航针对于 DrawLayout 抽屉式中导航栏没有很好的在一起合作。我们也注意到了这个问题,所以在最新的1.10的版本当中,我们对 DrawLayout 进行了优化,优化之后的 DrawLayout 的呈现形式是当用户第一次左滑的时候,你的抽屉栏会打开,然后用户再进行一次左滑,会退出当前应用。

这些对这些常见情景的优化,我们还在继续的优化过程当中,所以非常希望大家可以使用新的手势导航,并和我们分享任何的反馈或者疑问,或者你有什么样常见的情境被新的手势导航影响了。

Carousels 相关

另外一个常见的情境,如果你在使用 Carousels,应用内部有这种轮转式的显示的话,(一开始我们听到很多的开发者都在考虑,因为用户要拖拽,我要把整个区域边上的区域全部都覆盖掉,但其实我们推出之后,在内部做了一些研究,一些数据也显示,其实很快用户就习惯于这样一个手势操作,他明白他如果从最边界去拖拽的话,它是要返回的。其实用户对于新的的手势导航并没有认为有很大的冲突。)我们的建议就是如果你要用 Carousels ,不建议大家去覆盖那几个位置的手势导航,而是让用户去习惯于使用这样一个新的手势操作。

手势操作总结

今天介绍了很多关于全面屏和手势操作的建议,我在这里稍微总结一下:

1、随着新的全面屏导航的推出,越来越多的用户可能会使用新的导航方式,并且它会默认你的应用会和新的导航模式适配,所以如果你有任何不适配的地方,或者是故意不支持一些新的手势的地方,建议大家三思而后行。

2、有了全面屏就建议大家利用全面屏给用户带来一个更好的体验。紧接着为了帮助、确保像我刚才说的抽屉式导航等等这样的情境有很好的支持,我们会继续在Jetpack和MDC当中为大家加入新的更新和支持,欢迎大家去使用它们。

3、如果你要去覆盖手势区域的话,请确保是在有必要的情况下你才去覆盖。

Android Q Labs 直播专题页面

Android Q Labs 开场演讲

Android Q 有哪些更新

Android Q 现代化您的应用

后台 Activity 启动的限制

Android Q 分区存储

Jetpack 更新

Android Q 在折叠屏设备的适配

通用系统映像介绍

Google Play 商店政策

Android Q 地理位置权限变更

Android Q 深色主题

Android Q Labs 总结演讲