写点什么

在 -View- 上使用挂起函数,app 开发面试题及答案

用户头像
Android架构
关注
发布于: 刚刚

// 将该视图设置为不可见,再设置一些文字 titleView.isInvisible = truetitleView.text = "Hi everyone!"


// 等待下一次布局事件的任务,然后才可以获取该视图的高度 titleView.awaitNextLayout()


// 布局任务被执行// 现在,我们可以将视图设置为可见,并其向上平移,然后执行向下的动画 titleView.isVisible = truetitleView.translationY = -titleView.height.toFloat()titleView.animate().translationY(0f)}复制代码


我们为 View 的布局创建了一个 await 函数。用同样的方法可以替代很多常见的回调,比如?doOnPreDraw(),它是在 View 得到绘制时调用的方法;再比如?postOnAnimation(),在动画的下一帧开始时调用的方法,等等。

作用域

不知道您有没有发现这样一个问题,在上面的例子中,我们使用了?lifecycleScope?来启动协程,为什么要这样做呢?


为了避免发生内存泄漏,在我们操作 UI 的时候,选择合适的作用域来运行协程是极其重要的。幸运的是,我们的 View 有一些范围合适的?Lifecycle。我们可以使用扩展属性?lifecycleScope?来获得一个绑定生命周期的?CoroutineScope


LifecycleScope 被包含在 AndroidX 的 lifecycle-runtime-ktx 依赖库中,可以在这里找到 更多信息


我们最常用的生命周期的持有者 (lifecycle owner) 就是 Fragment 中的?viewLifecycleOwner),只要加载了 Fragment 的视图,它就会处于活跃状态。一旦 Fragment 的视图被移除,与之关联的?lifecycleScope?就会自动被取消。又由于我们已经为挂起函数中添加了对取消操作的支持,所以?lifecycleScope?被取消时,所有与之关联的协程都会被清除。

等待 Animator 执行完成

我们再来看一个例子来加深理解,这次是等待?Animator?执行结束:


suspend fun Animator.awaitEnd() = suspendCancellableCoroutine<Unit> { cont ->


// 增加一个处理协程取消的监听器,如果协程被取消,// 同时执行动画监听器的 onAnimationCancel() 方法,取消动画 cont.invokeOnCancellation { cancel() }


addListener(object : AnimatorListenerAdapter() {private var endedSuccessfully = true


override fun onAnimationCancel(animation: Animator) {// 动画已经被取消,修改是否成功结束的标志 endedSuccessfully = false}


override fun onAnimationEnd(animation: Animator) {


// 为了在协程恢复后的不发生泄漏,需要确保移除监听 animation.removeListener(this)if (cont.isActive) {


// 如果协程仍处于活跃状态 if (endedSuccessfully) {// 并且动画正常结束,恢复协程 cont.resume(Unit)} else {// 否则动画被取消,同时取消协程 cont.cancel()}}}})}复制代码


这个方法支持两个维度的取消,我们可以分别取消动画或者协程:


#1:?在 Animator 运行的时候,协程被取消 。我们可以通过?invokeOnCancellation?回调方法来监听协程何时被取消,这能让我们同时取消动画。


#2:?在协程被挂起的时候,Animator 被取消 。我们通过?onAnimationCancel())?回调来监听动画被取消的事件,通过调用协程的 cancel() 方法来取消挂起的协程。


这就是使用挂起函数等待方法执行来封装回调的基本使用了。??

组合使用

到这里,您可能有这样的疑问,"看起来不错,但是我能从中收获什么呢?" 单独使用其中某个方法,并不会产生多大的作用,但是如果把它们组合起来,便能发挥巨大的威力。


下面是一个使用?Animator.awaitEnd()?来依次运行 3 个动画的示例:


viewLifecycleOwner.lifecycleScope.launch {ObjectAnimator.ofFloat(imageView, View.ALPHA, 0f, 1f).run {start()awaitEnd()}


ObjectAnimator.ofFloat(imageView, View.TRANSLATION_Y, 0f, 100f).run {start()awaitEnd()}


ObjectAnimator.ofFloat(imageView, View.TRANSLATION_X, -100f, 0f).run {start()awaitEnd()}}复制代码


这是一个很常见的使用案例,您可以把这些动画放进?AnimatorSet?中来实现同样的效果。


但是这里使用的方法适用于不同类型的异步操作:?我们使用一个?ValueAnimator,一个?RecyclerView?的平滑滚动,以及一个?Animator?来举例:


viewLifecycleOwner.lifecycleScope.launch {// #1: ValueAnimatorimageView.animate().run {alpha(0


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


f)start()awaitEnd()}


// #2: RecyclerView smooth scrollrecyclerView.run {smoothScrollToPosition(10)// 该方法和其他方法类似,等待当前的滑动完成,我们不需要刻意关注实现// 代码可以在文末的引用中找到 awaitScrollEnd()}


// #3: ObjectAnimatorObjectAnimator.ofFloat(textView, View.TRANSLATION_X, -100f, 0f).run {start()awaitEnd()}}复制代码


试着用?AnimatorSet?实现一下吧??!如果不用协程,那就意味着我们要监听每一个操作,在回调中执行下一个操作,这回调层级想想都可怕。


通过把不同的异步操作转换为协程的挂起函数,我们获得了简洁明了地编排它们的能力。


我们还可以更进一步...


**如果我们希望?ValueAnimator?和平滑滚动同时开始,然后在两者都完成之后启动?ObjectAnimator,该怎么做呢?**那么在使用了协程之后,我们可以使用?async()?来并发地执行我们的代码:


viewLifecycleOwner.lifecycleScope.launch {val anim1 = async {imageView.animate().run {alpha(0f)start()awaitEnd()}}


val scroll = async {recyclerView.run {smoothScrollToPosition(10)awaitScrollEnd()}}


// 等待以上两个操作全部完成 anim1.await()scroll.await()


// 此时,anim1 和滑动都完成了,我们开始执行 ObjectAnimatorObjectAnimator.ofFloat(textView, View.TRANSLATION_X, -100f, 0f).run {start()awaitEnd()}}复制代码


但是如果您还想让滚动延迟执行怎么办呢? (类似?Animator.startDelay)?方法) 那么使用协程也有很好的实现,我们可以用?delay()?方法:


viewLifecycleOwner.lifecycleScope.launch {val anim1 = async {// ...}


val scroll = async {// 我们希望在 anim1 完成后,延迟 200ms 执行滚动 delay(200)


recyclerView.run {smoothScrollToPosition(10)awaitScrollEnd()}

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
在-View-上使用挂起函数,app开发面试题及答案