概述
嗯,先看需求:
整体需求是:监听麦克风,等待语音唤醒。唤醒成功后,展示弹窗,然后变成“我在听”的状态。当识别到用户说出的内容时,从服务器获取结果,并展示。整体包含了很多的动画效果,实现与之前写的 Android 动画序列的实现 类似,但还是写篇博客记录一下吧。
动画拆分
将视频看个好几遍,可以拆分出一些动画元素:
- 粒子大圆环效果;
- “我在听”文案的抖动效果;
- 听到内容后,文字变眼睛的效果;
- 机器人与眼睛一起缩放的效果;
- 语音文案与答案的展示效果;
- 底部原型按钮的效果;
拆分得差不多了,那么逐步实现即可。
动画 1
1 和 6 涉及到粒子效果,用原生 Android 来实现显然费时费力,而且效果不可预估,且具备很大的技术难度,基于我自身当前的技术深度,我只能先用 webp 或 gif 来实现了,让设计尽可能压缩,直接使用 Glide 加载即可。
动画 2
从视频中可以看出文案的抖动效果是平移,x、y 同时平移一个量,但是不能超出一个边界:
1 | /** |
动画 3
文字变眼睛效果,也就是 2 个 View 的 alpha 渐变联合起来:
1 | viewBinding.listenTv.animate().cancel() |
动画 4
机器人的图片与眼睛要达到一个一起缩放的效果,可以考虑当成一个 View,但是视频中显然这 2 个是不同的元素,因为眼睛有单独的动画效果,所以只能当成 2 个 View。那么要实现这样的一起缩放的效果,只能设置相同的缩放点:
1 | viewBinding.robotIv.pivotX = 140f |
robotIv 宽度为 280,取一半为中心点,140。eyeIv 宽度为 56,取一半为 28,这样设置的 pivotX 在屏幕上是一个同一个 X,Y 同理,如此可以实现一起缩放的效果。
1 | viewBinding.robotIv.animate() |
动画 5
语音文案与答案的动画效果就是个简单的 alpha 渐变:
1 | private fun showSpeak(speak: String) { |
tip 切换动画
“我在听”状态时,下面有提示语,有几条特定的提示语,有一个上下切换的效果,考虑使用 TextSwitcher 实现:
1 | viewBinding.tipTv.setFactory { |
设置好 Factory,以及出入的动画,调用 setText 方法即可有相应的上下切换效果:
1 | /** |
至此,所有的动画效果实现得差不多了,稍微调下优即可。
SDK 设计
公司内部希望将此功能作为一个单独的 Lib,那么有语音交互需求的 App 可单独集成。目前采用的是讯飞的 SDK,涉及到语音唤醒,语音听写,以及离线语音合成。讯飞 SDK 在内部是有根据 appId 来绑定 SDK 的,所以这个 Lib 无法提供具体的 SDK,SDK 必须由集成方自己去提供。所以这个 Lib 只能 compileOnly 讯飞的 SDK,以此来通过编译。
1 | // 编译依赖,具体的 SDK 由集成方提供 |
后续将讯飞 SDK 切换成其他的 SDK 也不是不可能,所以需要做个 SDK 隔离:
1 | /** |
然后提供一个讯飞 SDK 的实现:
1 | /** |
再提供一个 Manager 管理 Interactor:
1 | object VoiceInteractionManager { |
可以看到 interactor 直接是一个 IflytekInteractor 的对象,任何对 SDK 的调用统一由这个 manager 来做。这样即使后面切换 SDK,只需要将 IflytekInteractor 切换成另外一个 Interactor 即可。
另外 2 个封装的回调:
1 | /** |
另外,Lib 也会有部分资源文件,全部使用 voice__ 作为前缀开头,避免被集成方同名覆盖掉,产生不可预期的影响。至此,一个相对独立的 Lib 库设计基本完成,后续看需求进行迭代了。
后续
Lib 库提供 SNAPSHOT 版本时,集成方若要及时拉取,可以如此设置:
1 | configurations.all { |
动态版本即为 lastest 或者 + 表达式的版本,变化模块即为 SNAPSHOT。