Kotlin 有趣的扩展函数
Kotlin
是一个目标为Java平台上的新的编程语言。它是简洁、安全、实用和可以跟Java互操作。它能用到所有Java可以用到的地方: 服务端开发,Android应用开发,或者更多。Kotlin可以和所有存在的Java库和框架一起良好工作,有Java一样的效率
在kotlin中,函数和对象一样,都是“一等公民”,这也就表示在kotlin中,函数可以做变量能做的事情,如可以存储在变量与数据结构中、或是作为参数传递给其他高阶函数并且也可以作为高阶函数的返回值、也可以像其他任何非函数值一样调用函数
Kotlin扩展函数
Kotlin支持在不继承或者使用装饰模式的情况下对现有的类进行函数扩展,扩展后我们可以直接通过相应类调用的该扩展函数。函数中可以直接使用this访问到对象的成员变量
定义一个扩展函数
在Android中我们完成一个Toast操作如下,大部分情况我们通过定义utils来方便调用,在用kotlin我们就可以采用扩展函数的形式定义toast操作
// 扩展函数
inline fun Context.toast(msg: String) {
Toast.makeText(this, msg, Toast.LENGTH_LONG).show()
}
inline fun Fragment.toast(msg: String) {
Toast.makeText(activity, msg, Toast.LENGTH_LONG).show()
}
// 调用
toast("hello toast")
在这背后其实,kotlin做的也是将我们定义的扩展函数转换成一个工具类的形式,方法参数传入被扩展对象。只不过在使用上对开发者来说是透明的
标准库中的扩展函数
Kotlin为开发者提供了许多标准扩展函数,下面一起看看let、apply、also、run这四个扩展函数的具体定义以及使用
let扩展函数
方法定义
- T泛型:目标扩展对象
- R泛型:函数参数的返回值
- block函数:接口参数为T对象,返回值为R
- 执行block函数并返回其结果
public inline fun <T, R> T.let(block: (T) -> R): R {
return block(this)
}
使用例子
// 对变量进行判空、流程规范
listView?.let {
it.setFooterDividersEnabled(true)
it.setSelectionAfterHeaderView()
it.divider = null
var adapter = ArrayAdapter<String>(it.context, android.R.layout.simple_list_item_1)
it.adapter = adapter
adapter
}?.let {
it.addAll(listOf("item1", "item1"))
}
apply扩展函数
方法定义
- T泛型:目标扩展对象
- block函数:仍然为T的扩展函数,因此在函数体内可使用this或者直接引用目标对象的成员变量
- 执行block函数并返回目标对象自身
public inline fun <T> T.apply(block: T.() -> Unit): T {
block()
return this
}
使用例子
// 对象的构建,或者连续调用其多个方法,可省略前缀
var paint = Paint().apply {
isAntiAlias = true
color = Color.WHITE
xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN)
}
also扩展函数
方法定义
- T泛型:目标扩展对象
- block函数:接口参数为T对象,返回值为空
- 执行block函数并返回目标对象自身
public inline fun <T> T.also(block: (T) -> Unit): T {
block(this)
return this
}
使用例子
stuInfo.also {
it.append(stu?.name)
it.append(stu?.age)
it.append(stu?.code)
}.toString()
run扩展函数
方法定义
- T泛型:目标扩展对象
- R泛型:函数参数的返回值
- block函数:仍然为T的扩展函数,因此在函数体内可使用this或者直接引用目标对象的成员变量
- 执行block函数并返回其结果
public inline fun <T, R> T.run(block: T.() -> R): R {
return block()
}
使用例子
file?.run {
listFiles()
}?.run {
forEach {
it.delete()
}
}
关于inline关键字
Kotlin天生支持函数式编程,高阶函数和lambda是其一大特色
使用高阶函数会带来一些运行时间效率的损失:实际上调用一个lambda表达式编译时会生成一个匿名内部类并创建其对象,调用其函数来实现。但是如果使用inline关键字的话,当你调用一个inline function的时候,编译器不会产生一次函数调用,而是会在每次调用时,将inline function中的代码直接嵌入到调用处。
let、apply、also、run总结
从上面的各个例子可以可看到这几个扩展函数非常相似,只是在函数体中对于目标对象的引用方式以及函数返回值这两点的不同
扩展函数 | 引用目标对象方式 | 返回值 |
---|---|---|
let | it | 函数返回值 |
apply | this | 对象自身 |
also | it | 对象自身 |
run | this | 函数返回值 |
Android使用扩展函数
在开发过程中我们可以通过定义扩展函数提供各种更加高效的编码。Android KTX 是一组 Kotlin 扩展程序,属于 Android Jetpack 系列。它优化了供 Kotlin 使用的 Jetpack 和 Android 平台 API。Android KTX 旨在让您利用 Kotlin 语言功能(例如扩展函数/属性、lambda、命名参数和参数默认值),以更简洁、更愉悦、更惯用的方式使用 Kotlin 进行 Android 开发
下面我们来通过Android KTX中的例子来看看官方为我们定义的扩展函数
Animator动画监听
在不使用扩展函数时,我们监听动画是这样的
val animator = ObjectAnimator.ofFloat(view, "translationX", 0f, 100f)
animator.addListener {
object : Animator.AnimatorListener {
override fun onAnimationEnd(animation: Animator?) {
}
override fun onAnimationCancel(animation: Animator?) {
}
override fun onAnimationStart(animation: Animator?) {
}
override fun onAnimationRepeat(animation: Animator?) {
}
}
}
然后我们使用androidx.core.animation.Animator.kt中的扩展函数
val animator = ObjectAnimator.ofFloat(view, "translationX", 0f, 100f)
animator.addListener(onCancel = {
// something
})
果然简洁舒服许多,是怎么实现的呢。我们看看Animator.kt的源码
- 定义Animator的扩展函数addListener
- 接收onEnd、onStart、onCancel、onRepeat并指定默认值为空函数
- 扩展函数中创建AnimatorListener的匿名对象并将函数参数赋值给对应函数
- 那么在使用时我们只需要指定我们需要的函数即可
inline fun Animator.addListener(
crossinline onEnd: (animator: Animator) -> Unit = {},
crossinline onStart: (animator: Animator) -> Unit = {},
crossinline onCancel: (animator: Animator) -> Unit = {},
crossinline onRepeat: (animator: Animator) -> Unit = {}
): Animator.AnimatorListener {
val listener = object : Animator.AnimatorListener {
override fun onAnimationRepeat(animator: Animator) = onRepeat(animator)
override fun onAnimationEnd(animator: Animator) = onEnd(animator)
override fun onAnimationCancel(animator: Animator) = onCancel(animator)
override fun onAnimationStart(animator: Animator) = onStart(animator)
}
addListener(listener)
return listener
}
SharedPreferences
正常情况我们是这样使用它的
val sp = getSharedPreferences("test", Context.MODE_PRIVATE)
sp.edit().putString("name", "liu").commit()
sp.edit().putInt("age", 20).commit()
sp.edit().putBoolean("isMale", true).commit()
我们用扩展参数是这样的
sp.edit {
putString("name", "liu")
putInt("age", 20)
putBoolean("isMale", true)
}
扩展函数定义如下:
inline fun SharedPreferences.edit(
commit: Boolean = false,
action: SharedPreferences.Editor.() -> Unit
) {
val editor = edit()
action(editor)
if (commit) {
editor.commit()
} else {
editor.apply()
}
}
- 定义SharedPreferences 的edit扩展函数
- commit参数:是否及时commit提交到磁盘文件
- action参数:是SharedPreferences.Editor的扩展函数,需要传入Editor
自定义扩展函数
// 定义EditText扩展函数,方便监听TextChange
inline fun EditText.onTextChange(
crossinline afterChanged: (s: Editable?) -> Unit = {},
crossinline beforeChanged: (s: CharSequence?, start: Int, count: Int, after: Int) -> Unit = { s, start, couunt, after -> },
crossinline onChanged: (s: CharSequence?, start: Int, before: Int, count: Int) -> Unit = { s, start, before, count -> }
) {
val listener = object : TextWatcher {
override fun afterTextChanged(s: Editable?) = afterChanged(s)
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = beforeChanged(s, start, count, after)
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = onChanged(s, start, before, count)
}
addTextChangedListener(listener)
}
扩展函数中我们指定了参数的默认值,所以在使用时我们可以指定我们需要的函数即可。比如我们只需要监听onTextChanged
editText.onTextChange(
onChanged = { c: CharSequence?, i: Int, i1: Int, i2: Int ->
}
)
看着还是麻烦,我们再加个扩展函数
inline fun EditText.onChanged(
crossinline onChanged: (s: CharSequence?, start: Int, before: Int, count: Int) -> Unit = { _, _, _, _ -> }) {
onTextChange(onChanged = onChanged)
}
用的时候,我们单独调用onChanged方法
editText.onChanged { s, start, before, count ->
}