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 ->

        }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,951评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,606评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,601评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,478评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,565评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,587评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,590评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,337评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,785评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,096评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,273评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,935评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,578评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,199评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,440评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,163评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,133评论 2 352