最近处理了一个动画的问题,很有意思,这里记录一下
框架预览
现在我们有一个页面,底部是一个滚动的RecyclerView,每个item是一个program显示其图片,当选中任意一个program将会放大并获取焦点,整个背景要显示program对应的programCover封面,结构如下
所有动画类型
首先梳理下所有的动画,其中每个动画延迟都是500ms
背景切换动画,列表的焦点从cover1切换到cover2,cover1渐变消失,消失后显示cover2
cover隐藏动画,当选中的program触发了视频播放时,cover渐变消失,消失后看到视频播放
cover显示动画,当播放暂停后,cover渐变显示
每个动画启动时,都会cancel上一个动画
问题一:列表快速滚动
场景:快速滚动时丢掉中间动画
如果列表快速滚动,焦点不断变化,背景cover会快速不断的切换,如果从第1个快速切换到第7个,中间的动画需要省略,直接对第1个做淡出动画结束后显示第7个cover
实现
当列表在滚动的过程中,焦点变化收到onFocusChange,此时我们将新焦点的Image资源封装成一个Program.Switch类型的对象 交给StateFlow,我们调用的是StateFlow#update函数
另外有一个AnimationHelper在collect这个对象,收到之后就开始做动画,这个动画过程是一个挂起函数,动画结束之后才会接受下一个动画事件,这里利用了StateFlow的特性,当上一个动画挂起时,当有新的Program.Switch1 更新进来,然后Program.Switch2更新进来,此时第一个动画结束,collect收集到执行的动画是Program.Switch2, Switch1被丢弃了
问题二:Cover隐藏/显示动画
hideCover和showCover两个动画并没有加入到StateFlow这个队列里边
场景:cover消失了,透出了视频的第一帧
当触发了这样一个时序:暂停播放时先showCover,然后立即Switch到下一个Program, 由于showCover和Switch动画不在一个时间序列里边,当StateFlow缓存了下一个Switch动画,showCover开始执行,此时下一个Switch动画开始执行,动画开始时cancel上一个动画,即showCover动画被取消,Switch动画结束之后就看不到Cover了
实现
将hide/show这两个动画也添加到 StateFlow这个流里边去,让Switch动画和show动画顺序执行
同时还在Switch动画结束onEnd回调中将cover设置为VISIBLE, 这样也能很好的避免cover消失
问题三:cover和选中的不一致
StateFlow的特性会导致中间的数据流丢弃,collect到的总是最新数据
场景1: Switch被丢弃
当Switch1开始执行,Switch2进入队列,马上又更新一个Show给StateFlow, 当Switch1执行完之后,collect收到并执行的是Show动画,show动画结束之后看到就是Switch1切换之后的图片,和底部列表focus的不一致
实现
也就是StateFlow里边至少得缓存两个值,还得丢弃多余得值,我找到了一种解决办法,构建一个SharedFlow
val switchProgramAnim = MutableSharedFlow<SwitchProgram?>(
replay = 0,
extraBufferCapacity = 2,
onBufferOverflow = BufferOverflow.DROP_OLDEST)replay=0: 表示后续订阅调用collect不再收到之前的值
extraBufferCapacity=2: 缓存两个
DROP_OLDEST: 如果缓存满了2个则丢弃最早emit进去的值
场景2: Switch被Cancel
当播放按钮被点击,或者类容切换到播放模式(ContentPlaying非全屏播放),播放切换到暂停后的Content模式,都会去触发一次cancel背景cover的动画, 当program发生切换,cover开始做动画,然后快速的切换到播放-暂停,正在执行的Switch动画就会被取消,由于取消后在onCancel中并没有去重新加载一次最终的资源到cover, 也会出现背景和焦点program不一致的问题
实现
这个cancel是不必要的,将之移除,同时我们在动画onCancel回调中将最新的资源加载到cover控件上,即可正确显示