作者:Nick Butcher, Android 设计师 + 开发project师, Google
在为 Google I/O 2018 Android 团队工作期间,我们的主要作品之中的一个就是这个官方的应用,同意与会者和远程人员了解会议细节,建立个性化的时间表,并在会场预订座位。我们在应用中构建了很多有趣的动效,并且我们也开源了这个应用的代码,以下我想强调一些这些设计实例,以及一些有趣的实现细节,希望能给大家日常的动效设计带来灵感。
通常我们会在应用中使用 3 种类型的动效:
主动效:用于强化品牌视觉,并带来令人惊喜的视觉焦点
屏幕切换
状态变化
接下来,我想具体介绍一下这些动效的设计细节。
倒计时动画
能够这么说,Google I/O 官方应用的当中一部分作用就是为会议带来兴奋感和期待感。因此,我们在首屏幕和信息页面都显示了大型的倒计时动画。这也是将大会的活动品牌视觉进行呈现的绝佳机会,为应用增添了非常多特色。
△ 主屏幕抓眼球的倒计时动画
这个动效是由一位动效设计师设计的。并以一系列的 Lottie json 文件交到我们手中:每秒显示一个数字,让它以动效的形式 “in” 然后 “out”。Lottie 格式的文件能够轻松地放入 assets,甚至提供了便利的操作方法,如 setMinAndMaxProgress,它同意我们仅仅播放动效的前半部分或后半部分 (从而仅仅显示动效的进场或出场)。
真正好玩的地方是将多个单独的动画文件布局成总体的倒计时画面。
为此,我们创建了 CountdownView,一个相当复杂的自己定义 ConstraintLayout,当中包括了多个 LottieAnimationViews。在这里,我们创建了一个 Kotlin 托付来封装启动适当的动效。这样我们就能够简单地为每一个应该显示的数字的托付分配一个用来显示的 Int,随后托付就会完毕设置并启动动效。我们扩展了 ObservableProperty,确保我们仅仅在数字更改时才执行动效。我们的动效循环每秒仅仅公布一个可执行状态 (在和视图关联起来的时候)。这个状态计算每一个视图应该显示哪个数字并对托付进行更新。
预约会议
应用的关键操作之中的一个是让与会者预定座位。
因此,我们在会议详情屏幕上的 FAB 中突出显示此操作。
我们觉得应该仅当预定操作在后端成功完毕后再进行提示 (不像那些不太重要的操作。比如 “关注” 一场演讲。我们就差点儿是同步在界面上显示关注成功)。等待来自后端的响应可能须要一些时间。为了这个预定的过程感觉更快捷,我们使用动效图标,表明我们正在对预定进行处理,然后再平滑过渡到成功的状态中。
△ 预定的动画。有展示后端操作的动画过程
这个图标须要反映的状态非常复杂:会议可能是可预订的。可能已经预留了座位,假设座位已满。则可能产生等候名单,他们也可能出如今等待列表上。或是在会议即将開始时预约功能被禁用了。
这会导致应用须要显示多种动效以代表各种不同的状态。为了简化这些转场效果。我们决定始终显示 “后端操作” 状态,也就是上面动画中的沙漏。
因此。每次动效切换实际上都是成对的:状态 1 → 后端操作 & 后端操作 → 状态 2。这样就简化了非常多事情 (不然您能够想象可能出现的排列组合会有多少)。接下来我们使用 shapeshifter 构建了全部这些动效。
为了显示这些动效,我们使用了自己定义视图和 AnimatedStateListDrawable (ASLD)。假设你还没实用过 ASLD,我们简介一下:正如其名,它是动效版的 StateListDrawable,让你不仅能够为每一个状态提供不同的 Drawable,还能够提供过渡状态之间的转场 (以 AnimatedVectorDrawable 或 AnimationDrawable 的形式)。
我们创建了一个自己定义视图来支持这些自己定义的预定状态。
视图提供一些标准状态,如已按下或已选中。相同,您也能够定义自己的状态。并依据状态显示不同的 Drawable。我们自行定义了 state_reservable,state_reserved 等,随后我们会再为这些不同的状态创建一个枚举,封装视图状态以及不论什么相关的属性,如相关的内容描写叙述。
然后,我们的业务逻辑能够简单地从视图上的这个枚举中设置适当的值 (通过数据绑定)。这将更新 Drawable 的状态,然后通过 ASLD 启动动效。自己定义状态和 AnimatedStateListDrawable 是实现这一点的一种巧妙方法,将多个状态保留在声明层中。从而产生最少的视图代码。
演讲者动效
很多屏幕间的转场动画都能够直接用标准窗体动效。我们另辟蹊径的地方是。前往演讲者介绍信息屏幕的转场效果。例如以下图,演讲者头像在演讲信息和演讲者信息两个屏幕之间共享,是典型的共享元素转场动画,这样也有助于理解前后两个屏幕间的内容关联。
△ 演讲者头像在两个屏幕间共享
这里是一个非常标准的共享元素转换,在 ImageView 上使用了 ChangeBounds 和 ArcMotion 类。
更有意思的是。启动这个转场效果本身也会纳入我们的导航设计/开发模式中来。大体上讲,这样的模式将输入事件 (如点击一位演讲者) 与导航事件分离,让 ViewModel 来负责怎样响应输入。在这样的情况下,这样的解耦意味着,ViewModel 将会暴露出 Events 的 LiveData,它仅仅知道须要导航到哪个 ID 的演讲者。启动共享元素转场效果须要共享 View。而在这一瞬间这个 View 还没有。
我们解决问题的方式是,在绑定时将演讲者的 ID 作为标签存储在视图上,以便稍后当我们须要导航到特定的演讲者细节屏幕时检索该视图。
过滤器
会议应用的核心功能之中的一个是帮助用户从很多会议中过滤出感兴趣的那些。每一个话题都有一个与之相关的颜色。以便识别。我们收到的设计方案基于 “Chip” 动画,便于用户与各个话题进行操作。
△ Chip 列表非常适合用来挑选感兴趣的话题
Material Components 里提供了默认的 Chip 控件,但我们决定使用自己定义视图。以便更好地控制各个状态的显示内容和动效。
我们使用 canvas 进行画图并使用 StaticLayout 来显示文本。该视图具有单一的 progress 属性,即 0 - 未选中 / 1 - 已选中。在状态切换的时候,我们会同一时候改动视图中的显示元素。并使用线性插值来决定每一帧的形状和颜色。
最初在实现这个控件时,我让视图实现了 Checkable 接口,并在 setChecked 方法被调用来给状态赋新值的时候启动动效。只是当我们在 RecyclerView 中显示多个过滤器时。会出现用户的操作触发的动效和数据绑定触发的动效冲突的情况。为了解决问题,我们单独提供了一个触发动效的方法来避免这样的冲突。
此外,当我们刚刚给应用里引入这个动画效果时。我们发现它有丢帧。是我的动效代码有问题吗?这些控件位于一个 BottomSheet 中,且被放置在会议日程安排画面的前面。
当过滤器里的值被改动时。我们会执行过滤会议形成的业务逻辑,并且更新关注列表里的关注内容,这些看来都没什么问题。后来经过排查,我们确定问题在于。当过滤器里的内容被改动的时候。负责显示日程的 RecyclerViews 的 ViewPager 尽职尽责地启动了,并依据新提供的数据进行了更新。
这导致很多视图出现了由于内容添加而“膨胀”的现象。更由于这个视图内容的添加而触发了自适应布局的逻辑,来又一次渲染当中的内容。
全部的这些逻辑都是自己主动的,也没问题……除了会撑爆我们的刷新率。但关注列表事实上在过滤器的 “后面” (被遮住了),于是我们决定延迟执行关注列表里的内容更新 (直到过滤器里的值都被确定,且动效開始执行时才更新),我们牺牲了一些实现的复杂性。换取了更加顺畅的用户体验。最初我使用 postDelayed 实现它,但这导致了 UI 測试时出现故障。
于是,我们改动了启动动效的方法。同意它接受一个 lambda 表达式。这样一来,我们就能在保持用户操作的动效和高效的測试之间找到一个平衡。
一起动起来吧!
总的来说。我觉得动效确实有助于改善应用的体验,以及提升品牌表现力。
希望这篇文章能让您明确我们在各个环节使用动效的动机,以及具体实现它们的做法。更重要的是,我们希望您的应用中也能出现丰富多彩的动效,在抓人眼球的同一时候也能非常好地表达品牌信息和交互意图。
点击屏末 | 阅读原文 | 获取 “Google I/O 官方应用” 开源码。
推荐阅读: