Listeners with several functions in Kotlin

如何用 kotlin 简化有多个方法的接口。当只有一个方法的 Listeners(或者任何接口)很容易,会自动被替换成 lambda。但是有多个方法的 listeners 并不会自动替换。所以这篇文章将展示如何用 kotlin 解决这个问题。
这篇文章中使用到了 kotlin 的 函数类型扩展函数带接收者的函数字面量接口等知识点

The problem

当我们给 View 设置 OnclickListener 的时候 kotlin 会自动优化成 lambda 表达式

1
2
3
4
view.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?){
toast("View clicked!")}
})

简化成下面:

1
view.setOnClickListener {toast("View clicked!")}

但是如果接口里有多个方法并不能被自动优化。比如我们想给 view animation 设置 listener ,我们可以不是很优雅的写成如下样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
view.animate()
.alpha(0f)
.setListener(object : Animator.AnimatorListener {
override fun onAnimationStart(animation: Animator?) {
toast("Animation Start")
}
override fun onAnimationRepeat(animation: Animator?) {
toast("Animation Repeat")
}
override fun onAnimationEnd(animation: Animator?) {
toast("Animation End")
}
override fun onAnimationCancel(animation: Animator?) {
toast("Animation Cancel")
}
})

但是 Android 系统早已经有了解决方法:adapters。系统对大多数有多个方法的接口提供了一个对所有方法有个空实现的实现类。所以可以优化成下面的样子:

1
2
3
4
5
6
7
view.animate()
.alpha(0f)
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
toast("Animation End")
}
})

上面的代码优雅了一点点,但是还有几个问题:

  • 这些 adapters 都是 class,意味着不是扩展不是很方便
  • 使用匿名类对象和函数来实现,如果使用 lambda 会更优雅

Kotlin 里的接口

在 kotlin 中,接口除了包含抽象方法的声明,也包含实现。所以你可以声明一个可以被实现的 adapter

1
2
3
4
5
6
interface MyAnimatorListenerAdapter : Animator.AnimatorListener {
override fun onAnimationStart(animation: Animator) = Unit
override fun onAnimationRepeat(animation: Animator) = Unit
override fun onAnimationCancel(animation: Animator) = Unit
override fun onAnimationEnd(animation: Animator) = Unit
}

所有的方法有一个空的默认实现,所以一个类可以只去实现需要的方法:

1
2
3
4
5
6
class MainActivity : AppCompatActivity(), MyAnimatorListenerAdapter {
...
override fun onAnimationEnd(animation: Animator) {
toast("Animation End")
}
}

然后作为 listener 方法的参数来调用

1
2
3
view.animate()
.alpha(0f)
.setListener(this)

但是上面的实现还是需要去显示的声明方法,并没有用到 lambda,这和直接使用系统的 adapter 没什么区别。

扩展函数Extension functions

让我们看一下更优雅的解决方法。大多数情况下你可能只需要用到一个接口里的特定方法,对其他的并不感兴趣。比如 AnimatorListener 我们经常用到的就是 onAnimationEnd 。所以我们可以创建一个扩展函数,达到下面的效果:

1
2
3
view.animate()
.alpha(0f)
.onAnimationEnd { toast("Animation End") }

这个扩展函数被添加到 ViewPropertyAnimator 上,这个 Animator 对象是被方法 animate(),alpha() 方法返回的。

1
2
3
4
5
6
7
inline fun ViewPropertyAnimator.onAnimationEnd(crossinline continuation: (Animator) -> Unit) {
setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
continuation(animation)
}
})
}

可以看到,这个扩展方法接收一个 lambda 函数 当动画结束后这个函数会被调用。这个扩展函数帮我们做了点脏活:创建 adapter 和调用 setListener

命名参数和默认值named arguments and default values

为什么喜欢 kotlin 的理由之一就是它有很多神奇的功能可以优化我们的代码!下面我们可以使用 named arguments 来命名 lambdas 明确表达我们需要用那些方法,大大增加了可读性。扩展函数可以生命成如下样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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)
}
})
}

所以我们可以像下面这样去使用它:

1
2
3
4
5
6
7
8
view.animate()
.alpha(0f)
.setListener(
animationStart = { toast("Animation start") },
animationRepeat = { toast("Animation repeat") },
animationCancel = { toast("Animation cancel") },
animationEnd = { toast("Animation end") }
)

上面的解决方式需要我们实现所有方法。但是我们可以使用 参数的默认值 来优化:

1
2
3
4
5
6
7
8
inline fun ViewPropertyAnimator.setListener(
crossinline animationStart: (Animator) -> Unit = {},
crossinline animationRepeat: (Animator) -> Unit = {},
crossinline animationCancel: (Animator) -> Unit = {},
crossinline animationEnd: (Animator) -> Unit = {}) {
...
}

然后如下调用:

1
2
3
4
5
view.animate()
.alpha(0f)
.setListener(
animationEnd = { toast("Animation end") }
)

The killer option: DSLs

下面我们来看一种更加灵活的解决方案,我们可以创建一个 helper 实现一系列方法,这些方法接收 lambda 作为参数。这些 lambda 将会在动画事件接口中被调用。先来看一下优化后的结果,再来看是如何实现的:

1
2
3
4
5
6
7
8
9
10
view.animate()
.alpha(0f)
.setListener {
onAnimationStart {
toast("Animation start")
}
onAnimationEnd {
toast("Animation End")
}
}

我们设计了一些 animation 监听事件,我们只需要调用我们需要的方法就好了。这种做法可以死清楚的告诉开发者正在覆写哪个动画动作,比命名参数更加的直观。
下面我们来看一下如何实现的。首先我们还是需要一个扩展函数:

1
2
3
4
5
fun ViewPropertyAnimator.setListener(init: AnimListenerHelper.() -> Unit) {
val listener = AnimListenerHelper()
listener.init()
this.setListener(listener)
}

这个扩展函数接收一个带接收者的函数字面值 作为参数,然后创建了一个 AnimListenerHelper() 实例,去调用这个 lambda 函数。然后把该实例作为 listener 设置动画监听器。这里把扩展函数和带接收者的函数字面值放在一起刚开始不是很好的理解,先来看一下 AnimListenerHelper 的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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
fun onAnimationCancel(onAnimationCancel: AnimListener) {
animationCancel = onAnimationCancel
}
override fun onAnimationCancel(animation: Animator) {
animationCancel?.invoke(animation)
}
private var animationEnd: AnimListener? = null
fun onAnimationEnd(onAnimationEnd: AnimListener) {
animationEnd = onAnimationEnd
}
override fun onAnimationEnd(animation: Animator) {
animationEnd?.invoke(animation)
}
}
  • AnimListenerHelper 实现动画监听事件 Animator.AnimatorListener
  • 使用类型别名 声明了一个函数类型 AnimListener,这个函数类型接收一个 Animator 参数,没有返回值。
  • 定义了相应的 onAnimationXXX 方法,接收上面声明的 AnimListener 函数类型作为参数,并赋值。
  • 覆写动画的系统回调方法,调用相应的函数类型变量的 invoke() 方法执行对应的函数

然后看下面这段代码,给 ViewPropertyAnimator 添加扩展函数 setListener,接收一个带 AnimListenerHelper 接收者的函数字面值(函数内部提供了一个 AnimListenerHelper 对象)作为参数。在这个函数内部调用接收者对象的成员可以无需任何额外的限定符。调用带有接收者的函数类型值的方法之一就是在其前面加上接收者对象:

1
2
3
4
5
fun ViewPropertyAnimator.setListener(init: AnimListenerHelper.() -> Unit) {
val listener = AnimListenerHelper()
listener.init()
this.setListener(listener)
}

所以再回到最初调用 setListener() 的地方,如果写成下面的形式就会好理解很对:

1
2
3
4
5
6
7
val listener: AnimListenerHelper.()->Unit = {
this.onAnimationStart { toast("animator start") }
this.onAnimationEnd { toast("animator end") }
}
view.animate()
.alpha(0f)
.setListener (listener)

https://antonioleiva.com/listeners-several-functions-kotlin/

https://www.kotlincn.net/docs/reference/