用 Kotlin 可取消协程扩展替换动画回调(暂停视图)
Replace animation callbacks with Kotlin cancellable coroutine extensions (Suspending over views)
所以我读了 Chris Banes post here 的这篇很棒的媒体 here,它解释了协程或特定的挂起函数如何用于协调动画,而没有你可能陷入的“回调地狱”将动画链接在一起。我设法让 onAnimationEnd
侦听器扩展按照他的示例工作,但我似乎无法为 onAnimationStart
侦听器做同样的事情,这是我的 awaitEnd
方法
suspend fun Animator.awaitEnd() = suspendCancellableCoroutine<Unit> { continuation ->
continuation.invokeOnCancellation { cancel() }
this.addListener(object : AnimatorListenerAdapter() {
private var endedSuccessfully = true
override fun onAnimationCancel(animation: Animator) {
endedSuccessfully = false
}
override fun onAnimationEnd(animation: Animator) {
animation.removeListener(this)
if (continuation.isActive) {
// If the coroutine is still active...
if (endedSuccessfully) {
// ...and the Animator ended successfully, resume the coroutine
continuation.resume(Unit)
} else {
// ...and the Animator was cancelled, cancel the coroutine too
continuation.cancel()
}
}
}
})
}
在下面的代码中,我在异步块中使用它并首先等待动画完成,然后等待协程完成
val anim = async {
binding.splash.circleReveal(null, startAtX = x, startAtY = y).run {
start()
//...doStuff()
awaitEnd()
}
}
anim.await()
这很好用,将日志添加到正确的函数表明一切都按预期被调用,现在以相同的方式添加启动的扩展...
suspend fun Animator.started() = suspendCancellableCoroutine<Unit> { continuation ->
continuation.invokeOnCancellation { cancel() }
this.addListener(object : AnimatorListenerAdapter() {
private var endedSuccessfully = true
override fun onAnimationCancel(animation: Animator?) {
endedSuccessfully = false
}
override fun onAnimationStart(animation: Animator?) {
Log.d("DETAIL", "Animator.started() onAnimationStart")
animation?.removeListener(this)
if (continuation.isActive) {
// If the coroutine is still active...
if (endedSuccessfully) {
// ...and the Animator ended successfully, resume the coroutine
continuation.resume(Unit)
} else {
// ...and the Animator was cancelled, cancel the coroutine too
continuation.cancel()
}
}
}
})
}
并在 start 方法之后从同一个异步块中调用它(这可能与事情有关)
val anim = async {
binding.splash.circleReveal(null, startAtX = x, startAtY = y).run {
start()
started()
//...doStuff()
awaitEnd()
}
}
anim.await()
现在发生的事情是开始的协程被挂起但永远不会恢复,如果我添加一些日志语句我可以看到它调用 start()
,然后它调用 started()
但后来没有更进一步,显然我已经尝试更改操作顺序但无济于事,有人能看出我在这里做错了什么吗?
非常感谢
编辑
我也试过这个来添加 sharedElementEnter 转换,但它对我来说还是行不通,
suspend fun TransitionSet.awaitTransitionEnd() = suspendCancellableCoroutine<Unit> { continuation ->
val listener = object : TransitionListenerAdapter() {
private var endedSuccessfully = true
override fun onTransitionCancel(transition: Transition) {
super.onTransitionCancel(transition)
endedSuccessfully = false
}
override fun onTransitionEnd(transition: Transition) {
super.onTransitionEnd(transition)
Log.d("DETAIL","enterTransition onTransitionEnd")
transition.removeListener(this)
if (continuation.isActive) {
// If the coroutine is still active...
if (endedSuccessfully) {
// ...and the Animator ended successfully, resume the coroutine
continuation.resume(Unit)
} else {
// ...and the Animator was cancelled, cancel the coroutine too
continuation.cancel()
}
}
}
}
continuation.invokeOnCancellation { removeListener(listener) }
this.addListener(listener)
}
并再次尝试使用 await 方法
viewLifecycleOwner.lifecycleScope.launch {
val sharedElementEnterTransitionAsync = async {
sharedElementEnterTransition = TransitionInflater.from(context)
.inflateTransition(R.transition.shared_element_transition)
(sharedElementEnterTransition as TransitionSet).awaitTransitionEnd()
}
sharedElementEnterTransitionAsync.await()
}
你是对的 - 协程永远不会恢复。
为什么?
当您调用 started()
方法时动画已经开始。这意味着 started()
中的侦听器中定义的 onAnimationStart
将不会被调用 (因为它已经被调用) 将底层协程置于永远等待状态。
如果在 start()
之前调用 started()
也会发生同样的情况:底层协程将等待 onAnimationStart
被调用,但它永远不会发生,因为 start()
方法调用被 started()
方法创建的协程阻塞。
差不多是dead-lock.
解决方案 1
在started()
之前调用start()
returns:
suspend fun Animator.started() = suspendCancellableCoroutine<Unit> { continuation ->
continuation.invokeOnCancellation { cancel() }
this.addListener(object : AnimatorListenerAdapter() {
...
})
// Before the coroutine is even returned we should start the animation
start()
}
解决方案 2
传入一个函数(可选地接受一个 CancellableContinuation<Unit>
参数):
suspend fun Animator.started(executeBeforeReturn: (CancellableContinuation<Unit>) -> Unit) = suspendCancellableCoroutine<Unit> { continuation ->
continuation.invokeOnCancellation { cancel() }
this.addListener(object : AnimatorListenerAdapter() {
...
})
executeBeforeReturn(continuation)
}
它将允许您:
- 甚至在开始动画之前使用协程(例如取消);
- 避免锁定。
示例:
val anim = async {
ObjectAnimator.ofFloat(view, View.ALPHA, 0f, 1f).run {
started { continuation ->
if (anything) {
continuation.cancel()
}
start()
}
awaitEnd()
}
}
anim.await()
所以我读了 Chris Banes post here 的这篇很棒的媒体 here,它解释了协程或特定的挂起函数如何用于协调动画,而没有你可能陷入的“回调地狱”将动画链接在一起。我设法让 onAnimationEnd
侦听器扩展按照他的示例工作,但我似乎无法为 onAnimationStart
侦听器做同样的事情,这是我的 awaitEnd
方法
suspend fun Animator.awaitEnd() = suspendCancellableCoroutine<Unit> { continuation ->
continuation.invokeOnCancellation { cancel() }
this.addListener(object : AnimatorListenerAdapter() {
private var endedSuccessfully = true
override fun onAnimationCancel(animation: Animator) {
endedSuccessfully = false
}
override fun onAnimationEnd(animation: Animator) {
animation.removeListener(this)
if (continuation.isActive) {
// If the coroutine is still active...
if (endedSuccessfully) {
// ...and the Animator ended successfully, resume the coroutine
continuation.resume(Unit)
} else {
// ...and the Animator was cancelled, cancel the coroutine too
continuation.cancel()
}
}
}
})
}
在下面的代码中,我在异步块中使用它并首先等待动画完成,然后等待协程完成
val anim = async {
binding.splash.circleReveal(null, startAtX = x, startAtY = y).run {
start()
//...doStuff()
awaitEnd()
}
}
anim.await()
这很好用,将日志添加到正确的函数表明一切都按预期被调用,现在以相同的方式添加启动的扩展...
suspend fun Animator.started() = suspendCancellableCoroutine<Unit> { continuation ->
continuation.invokeOnCancellation { cancel() }
this.addListener(object : AnimatorListenerAdapter() {
private var endedSuccessfully = true
override fun onAnimationCancel(animation: Animator?) {
endedSuccessfully = false
}
override fun onAnimationStart(animation: Animator?) {
Log.d("DETAIL", "Animator.started() onAnimationStart")
animation?.removeListener(this)
if (continuation.isActive) {
// If the coroutine is still active...
if (endedSuccessfully) {
// ...and the Animator ended successfully, resume the coroutine
continuation.resume(Unit)
} else {
// ...and the Animator was cancelled, cancel the coroutine too
continuation.cancel()
}
}
}
})
}
并在 start 方法之后从同一个异步块中调用它(这可能与事情有关)
val anim = async {
binding.splash.circleReveal(null, startAtX = x, startAtY = y).run {
start()
started()
//...doStuff()
awaitEnd()
}
}
anim.await()
现在发生的事情是开始的协程被挂起但永远不会恢复,如果我添加一些日志语句我可以看到它调用 start()
,然后它调用 started()
但后来没有更进一步,显然我已经尝试更改操作顺序但无济于事,有人能看出我在这里做错了什么吗?
非常感谢
编辑
我也试过这个来添加 sharedElementEnter 转换,但它对我来说还是行不通,
suspend fun TransitionSet.awaitTransitionEnd() = suspendCancellableCoroutine<Unit> { continuation ->
val listener = object : TransitionListenerAdapter() {
private var endedSuccessfully = true
override fun onTransitionCancel(transition: Transition) {
super.onTransitionCancel(transition)
endedSuccessfully = false
}
override fun onTransitionEnd(transition: Transition) {
super.onTransitionEnd(transition)
Log.d("DETAIL","enterTransition onTransitionEnd")
transition.removeListener(this)
if (continuation.isActive) {
// If the coroutine is still active...
if (endedSuccessfully) {
// ...and the Animator ended successfully, resume the coroutine
continuation.resume(Unit)
} else {
// ...and the Animator was cancelled, cancel the coroutine too
continuation.cancel()
}
}
}
}
continuation.invokeOnCancellation { removeListener(listener) }
this.addListener(listener)
}
并再次尝试使用 await 方法
viewLifecycleOwner.lifecycleScope.launch {
val sharedElementEnterTransitionAsync = async {
sharedElementEnterTransition = TransitionInflater.from(context)
.inflateTransition(R.transition.shared_element_transition)
(sharedElementEnterTransition as TransitionSet).awaitTransitionEnd()
}
sharedElementEnterTransitionAsync.await()
}
你是对的 - 协程永远不会恢复。
为什么?
当您调用 started()
方法时动画已经开始。这意味着 started()
中的侦听器中定义的 onAnimationStart
将不会被调用 (因为它已经被调用) 将底层协程置于永远等待状态。
如果在 start()
之前调用 started()
也会发生同样的情况:底层协程将等待 onAnimationStart
被调用,但它永远不会发生,因为 start()
方法调用被 started()
方法创建的协程阻塞。
差不多是dead-lock.
解决方案 1
在started()
之前调用start()
returns:
suspend fun Animator.started() = suspendCancellableCoroutine<Unit> { continuation ->
continuation.invokeOnCancellation { cancel() }
this.addListener(object : AnimatorListenerAdapter() {
...
})
// Before the coroutine is even returned we should start the animation
start()
}
解决方案 2
传入一个函数(可选地接受一个 CancellableContinuation<Unit>
参数):
suspend fun Animator.started(executeBeforeReturn: (CancellableContinuation<Unit>) -> Unit) = suspendCancellableCoroutine<Unit> { continuation ->
continuation.invokeOnCancellation { cancel() }
this.addListener(object : AnimatorListenerAdapter() {
...
})
executeBeforeReturn(continuation)
}
它将允许您:
- 甚至在开始动画之前使用协程(例如取消);
- 避免锁定。
示例:
val anim = async {
ObjectAnimator.ofFloat(view, View.ALPHA, 0f, 1f).run {
started { continuation ->
if (anything) {
continuation.cancel()
}
start()
}
awaitEnd()
}
}
anim.await()