当 Kotlin 中的监听器包含多个方法时,如何让它 “巧夺天工”?

override fun onAnimationRepeat(animation: Animator) = Unit
override fun onAnimationCancel(animation: Animator) = Unit
override fun onAnimationEnd(animation: Animator) = Unit
}

有了这个,默认情况下所有方法都不会执行任何操作,这意味着一个类可以实现此接口并仅声明它所需的方法:

class MainActivity : AppCompatActivity(), MyAnimatorListenerAdapter {

override fun onAnimationEnd(animation: Animator) {
toast(“Animation End”)
}
}

之后,您可以将它作为监听器的参数:

view.animate()
.alpha(0f)
.setListener(this)

这个方案解决了开始时提出的一个问题,但是我们仍然要显式地声明它。如果我想使用 lambda 表达式呢?

此外,虽然这可能会不时地使用继承,但在大多数情况下,您仍将使用匿名对象,这与使用 framework 适配器并无不同。

但是,这是一个有趣的想法:如果你需要为具有多个方法的监听器定义一种适配器,那么最好使用接口而不是抽象类继承 FTW 的构成

一般情况下的扩展功能

让我们转向更加简洁的解决方案。可能会碰到这种情况(如上所述):大多数时候你只需要相同的功能,而对另一个功能则不太感兴趣。对于 AnimatorListener,最常用的一个方法通常是 onAnimationEnd。那么为什么不创建一个涵盖这种情况的扩展方法呢?

view.animate()
.alpha(0f)
.onAnimationEnd { toast(“Animation End”) }

真棒!扩展函数应用于 ViewPropertyAnimator,这是 animate()alpha 和所有其他动画方法返回的内容。

inline fun ViewPropertyAnimator.onAnimationEnd(crossinline continuation: (Animator) -> Unit) {
setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
continuation(animation)
}
})
}

之前已经谈过 内联,但如果你还有一些疑问,我建议你看一下官方的文档

如您所见,该函数只接收在动画结束时调用的 lambda。这个扩展函数为我们完成了创建适配器并调用 setListener 这种不友好的工作。

这样就好多了!我们可以在监听器中为每个方法创建一个扩展方法。但在这种特殊情况下,我们遇到了动画只接受一个监听器的问题。因此我们一次只能使用一个。

在任何情况下,对于大多数重复的情况(像上面那样),它并不会损害到像如上提到的 Animator 本身的方法。这是更简单的解决方案,非常易于阅读和理解。

使用命名参数和默认值

但是你和我喜欢 Kotlin 的原因之一是它有很多令人惊奇的功能来简化我们的代码!所以你可以想象我们还有一些选择的余地。接下来我们将使用命名参数:这允许我们定义 lambda 表达式并明确说明它们的用途,这将极大地提高代码的可读性。

我们会有类似于上面的功能,但涵盖所有方法的情况:

inline fun ViewPropertyAnimator.setListener(
crossinline animationStart: (Animator) -> Unit,
crossinline animationRepeat: (Animator) -> Unit,
crossinline animationCancel: (Animator) -> Unit,
crossinline animationEnd: (Animator) -> Unit) {

setListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator) {
animationStart(animation)
}

override fun onAnimationRepeat(animation: Animator) {
animationRepeat(animation)
}

override fun onAnimationCancel(animation: Animator) {
animationCancel(animation)
}

override fun onAnimationEnd(animation: Animator) {
animationEnd(animation)
}
})
}

方法本身不是很好,但通常是伴随扩展方法的情况。他们隐藏了 framework 不好的部分,所以有人必须做艰苦的工作。现在您可以像这样使用它:

view.animate()
.alpha(0f)
.setListener(
animationStart = { toast(“Animation start”) },
animationRepeat = { toast(“Animation repeat”) },
animationCancel = { toast(“Animation cancel”) },
animationEnd = { toast(“Animation end”) }
)

感谢命名参数,让我们可以很清楚这里发生了什么。

你需要确保没有命名参数的时候就不要使用它,否则它会变得有点乱:

view.animate()
.alpha(0f)
.setListener(
{ toast(“Animation start”) },
{ toast(“Animation repeat”) },
{ toast(“Animation cancel”) },
{ toast(“Animation end”) }
)

无论如何,这个解决方案仍然迫使我们实现所有方法。但它很容易解决:只需使用参数的默认值。空的 lambda 表达式将上面的代码演变成:

inline fun ViewPropertyAnimator.setListener(
crossinline animationStart: (Animator) -> Unit = {},
crossinline animationRepeat: (Animator) -> Unit = {},
crossinline animationCancel: (Animator) -> Unit = {},
crossinline animationEnd: (Animator) -> Unit = {}) {


}

现在你可以这样做:

view.animate()
.alpha(0f)
.setListener(
animationEnd = { toast(“Animation end”) }
)

还不错,对吧?虽然比之前的做法要稍微复杂一点,但却更加灵活了。

杀手锏操作:DSL

到目前为止,我一直在解释简单的解决方案,诚实地说可能涵盖大多数情况。但如果你想发疯,你甚至可以创建一个让事情变得更加明确的小型 DSL。

这个想法 来自 Anko 如何实现一些侦听器,它是创建一个实现了一组接收 lambda 表达式的方法帮助器。这个 lambda 将在接口的相应实现中被调用。我想首先向您展示结果,然后解释使其实现的代码:

view.animate()
.alpha(0f)
.setListener {
onAnimationStart {
toast(“Animation start”)
}
onAnimationEnd {
toast(“Animation End”)
}
}

看到了吗? 这里使用了一个小型的 DSL 来定义动画监听器,我们只需调用我们需要的功能即可。对于简单的行为,这些方法可以是单行的:

view.animate()
.alpha(0f)
.setListener {
onAnimationStart { toast(“Start”) }
onAnimationEnd { toast(“End”) }
}

这相比于之前的解决方案有两个优点:

  • 它更加简洁:您在这里保存了一些特性,但老实说,仅仅因为这个还不值得努力。
  • 它更加明确:它迫使开发人员说出他们所重写的功能。在前一个选择中,由开发人员设置命名参数。这里没有选择,只能调用该方法。

所以它本质上是一个不太容易出错的解决方案。

现在来实现它。首先,您仍需要一个扩展方法:

fun ViewPropertyAnimator.setListener(init: AnimListenerHelper.() -> Unit) {
val listener = AnimListenerHelper()
listener.init()
this.setListener(listener)
}

这个方法只获取一个带有接收器的 lambda 表达式,它应用于一个名为 AnimListenerHelper 的新类。它创建了这个类的一个实例,使它调用 lambda 表达式,并将实例设置为监听器,因为它正在实现相应的接口。让我们看看如何实现 AnimeListenerHelper

class AnimListenerHelper : Animator.AnimatorListener {

}

然后对于每个方法,它需要:

  • 保存 lambda 表达式的属性
  • DSL 方法,它接收在调用原始接口的方法时执行的 lambda 表达式
  • 在原有接口基础上重写方法

private var animationStart: AnimListener? = null

fun onAnimationStart(onAnimationStart: AnimListener) {
animationStart = onAnimationStart
}

override fun onAnimationStart(animation: Animator) {
animationStart?.invoke(animation)
}

这里我使用的是 AnimListener 的一个 类型别名

private typealias AnimListener = (Animator) -> Unit

这里是完整的代码:

fun ViewPropertyAnimator.setListener(init: AnimListenerHelper.() -> Unit) {
val listener = AnimListenerHelper()
listener.init()
this.setListener(listener)
}

private typealias AnimListener = (Animator) -> Unit

class AnimListenerHelper : Animator.AnimatorListener {

private var animationStart: AnimListener? = null

fun onAnimationStart(onAnimationStart: AnimListener) {
animationStart = onAnimationStart
}

override fun onAnimationStart(animation: Animator) {
animationStart?.invoke(animation)
}

private var animationRepeat: AnimListener? = null

fun onAnimationRepeat(onAnimationRepeat: AnimListener) {
animationRepeat = onAnimationRepeat
}

override fun onAnimationRepeat(animation: Animator) {
animationRepeat?.invoke(animation)
}

private var animationCancel: AnimListener? = null

最后

愿你有一天,真爱自己,善待自己。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值