这是该系列的第四篇,系列文章目录以下:java
即便不须要 Java 接口中的某些方法,也必须将其implements,而后保持其为空实现,傻傻地处在那。利用 Kotlin 的 DSL 能够只实现本身感兴趣的方法。函数
(这篇将在上一篇的代码基础上新增功能,并利用自定义的 DSL 来简化代码。)post
上篇中利用apply()
语法来简化组合动画的构建过程,代码以下:
val span = 300
AnimatorSet().apply {
playTogether(
ObjectAnimator.ofPropertyValuesHolder(
tvTitle,
PropertyValuesHolder.ofFloat("alpha", 0f, 1.0f),
PropertyValuesHolder.ofFloat("translationY", 0f, 100f)).apply {
interpolator = AccelerateInterpolator()
duration = span
},
ObjectAnimator.ofPropertyValuesHolder(
ivAvatar,
PropertyValuesHolder.ofFloat("alpha", 1.0f, 0f),
PropertyValuesHolder.ofFloat("translationY", 0f,100f)).apply {
interpolator = AccelerateInterpolator()
duration = span
}
)
start()
}
复制代码
若是动画的时间被拉长,须要在其暂停时显示 toast 提示,而且在结束时展现视图A,代码需作以下修改:
val span = 5000
AnimatorSet().apply {
playTogether(
ObjectAnimator.ofPropertyValuesHolder(
tvTitle,
PropertyValuesHolder.ofFloat("alpha", 0f, 1.0f),
PropertyValuesHolder.ofFloat("translationY", 0f, 100f)).apply {
interpolator = AccelerateInterpolator()
duration = span
},
ObjectAnimator.ofPropertyValuesHolder(
ivAvatar,
PropertyValuesHolder.ofFloat("alpha", 1.0f, 0f),
PropertyValuesHolder.ofFloat("translationY", 0f,100f)).apply {
interpolator = AccelerateInterpolator()
duration = span
}
)
addPauseListener(object :Animator.AnimatorPauseListener{
override fun onAnimationPause(animation: Animator?) {
Toast.makeText(context,"pause",Toast.LENGTH_SHORT).show()
}
override fun onAnimationResume(animation: Animator?) {
}
})
addListener(object : Animator.AnimatorListener{
override fun onAnimationRepeat(animation: Animator?) {
}
override fun onAnimationEnd(animation: Animator?) {
showA()
}
override fun onAnimationCancel(animation: Animator?) {
}
override fun onAnimationStart(animation: Animator?) {
}
})
start()
}
复制代码
这一段apply()
有点过长了,严重下降了它的可读性。罪魁祸首是 java 接口。虽然只用到接口中的一个方法,但却必须将其他的方法保留空实现。
有没有什么办法只实现想要的方法,去掉不用的方法?
利用 kotlin 的自定义 DSL 就能够实现。
DSL = domain specific language,即“特定领域语言”,与它对应的一个概念叫“通用编程语言”,通用编程语言有一系列完善的能力来解决几乎全部能被计算机解决的问题,像 Java 就属于这种类型。而特定领域语言只专一于特定的任务,好比 SQL 只专一于操纵数据库,HTML 只专一于表述超文本。
既然通用编程语言可以解决全部的问题,那为啥还须要特定领域语言?由于它能够使用比通用编程语言中等价代码更紧凑的语法来表达特定领域的操做。好比当执行一条 SQL 语句时,不须要从声明一个类及其方法开始。
更紧凑的语法意味着更简洁的 API。应用程序中每一个类都提供了其余类与之交互的可能性,确保这些交互易于理解并能够简洁地表达,对于软件的可维护性相当重要。
DSL 有一个普通API不具有特征:DSL 具备结构。而带接收者的lambda
使得构建结构化的 API 变得容易。
它是一种特殊的 lambda,是 kotlin 中特有的。能够把它理解成“为接收者声明的一个匿名扩展函数”。(扩展函数是一种在类体外为类添加功能的特性)
带接收者的lambda的函数体除了能访问其所在类的成员外,还能访问接收者的全部非私有成员,这个特性是它可以轻松地构建结构。
当带接收者的 lambda 配合高阶函数时,构建结构化的 API 就变得易如反掌。
它是一种特殊的函数,它的参数或者返回值是另外一个函数。
好比集合的扩展函数filter()
就是一个高阶函数:
//'filter的参数是一个带接收的lambda'
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
return filterTo(ArrayList<T>(), predicate)
}
复制代码
能够使用它来过滤集合中的元素:
students.filter { age > 18 }
复制代码
这样就是一种结构化 API 的调用(在 java 中看不到),虽然这种结构得益于 kotlin 的一个约定(若是函数只有一个参数且它是 lambda,则能够省略函数参数列表的括号)。但更关键的是 lambda 的内部,得益于带接收者的lambda
,age > 18
运行在一个和其调用方不一样的上下文
中,在这个上下文中,能够轻松的访问到Student
的成员Student.age
( 指向 age 时能够省略 this )
让咱们使用这样的技巧来解决“必须实现java全部接口”的问题。
class AnimatorListenerImpl {
var onRepeat: ((Animator) -> Unit)? = null
var onEnd: ((Animator) -> Unit)? = null
var onCancel: ((Animator) -> Unit)? = null
var onStart: ((Animator) -> Unit)? = null
}
复制代码
它包含四个成员,每一个成员的类型都是函数类型
。看一下Animator.AnimatorListener
的定义就能理解AnimatorListenerImpl
的用意:
public static interface AnimatorListener {
void onAnimationStart(Animator animation);
void onAnimationEnd(Animator animation);
void onAnimationCancel(Animator animation);
void onAnimationRepeat(Animator animation);
}
复制代码
该接口中的每一个方法都接收一个Animator
参数并返回空值,用 lambda 能够表达成 (Animator) -> Unit
。因此AnimatorListenerImpl
将接口中的四个方法的实现都保存在函数变量中,而且实现是可空的。
fun AnimatorSet.addListener(action: AnimatorListenerImpl.() -> Unit) {
AnimatorListenerImpl().apply { action }.let { builder ->
//'将回调实现委托给AnimatorListenerImpl的函数类型变量'
addListener(object : Animator.AnimatorListener {
override fun onAnimationRepeat(animation: Animator?) {
animation?.let { builder.onRepeat?.invoke(animation) }
}
override fun onAnimationEnd(animation: Animator?) {
animation?.let { builder.onEnd?.invoke(animation) }
}
override fun onAnimationCancel(animation: Animator?) {
animation?.let { builder.onCancel?.invoke(animation) }
}
override fun onAnimationStart(animation: Animator?) {
animation?.let { builder.onStart?.invoke(animation) }
}
})
}
}
复制代码
为Animator
定义了扩展函数addListener()
,该函数接收一个带接收者的lambdaaction
。
扩展函数体中构建了AnimatorListenerImpl
实例并紧接着应用了action
,最后为Animator
设置动画监听器并将回调的实现委托给AnimatorListenerImpl
中的函数类型变量。
val span = 5000
AnimatorSet().apply {
playTogether(
ObjectAnimator.ofPropertyValuesHolder(
tvTitle,
PropertyValuesHolder.ofFloat("alpha", 0f, 1.0f),
PropertyValuesHolder.ofFloat("translationY", 0f, 100f)).apply {
interpolator = AccelerateInterpolator()
duration = span
},
ObjectAnimator.ofPropertyValuesHolder(
ivAvatar,
PropertyValuesHolder.ofFloat("alpha", 1.0f, 0f),
PropertyValuesHolder.ofFloat("translationY", 0f,100f)).apply {
interpolator = AccelerateInterpolator()
duration = span
}
)
addPauseListener{
onPause = { Toast.makeText(context,"pause",Toast.LENGTH_SHORT).show() }
}
addListener {
onEnd = { showA() }
}
start()
}
复制代码
(省略了扩展函数addPauseListener()
的定义,它和addListener()
是相似的。)
得益于带接收者的lambda
,能够轻松地为AnimatorListenerImpl
的成员onEnd
赋值,这段逻辑会在动画结束时被调用。
这段调用拥有本身独特的结构,它解决了“必须实现所有 java 接口”这个特定的问题,因此它能够称得上是一个自定义 DSL 。(固然和 SQL 相比,它显得太简单了)。
下一篇会进一步使用 DSL 的思想将 Android 整套构建动画的接口重构成结构化的代码。到时候就能够使用这样的代码来构建动画:
animSet {
objectAnim {
target = textView
scaleX = floatArrayOf(1.0f,1.3f)
scaleY = scaleX
duration = 300L
interpolator = LinearInterpolator()
} with objectAnim {
target = button
translationX = floatArrayOf(0f,100f)
duration = 300
interpolator = LinearInterpolator()
} before anim{
values = intArrayOf(ivRight,screenWidth)
action = { value -> imageView.right = value as Int }
duration = 400
interpolator = LinearInterpolator()
}
onEnd = Toast.makeText(activity,"animation end",Toast.LENGTH_SHORT).show()
start()
}
复制代码