Kotlin:扩展函数和高阶函数的运用

前言

在上一篇文章中我们详细的介绍了Kotlin中三个常用的关键字:inline、noinline、crossinline。本篇文章笔者打算结合前面几篇文章的介绍,来讲解我们在实际开发中的运用。通常扩展函数和高阶函数都会结合着使用,这在很大程度上可以简化代码。下面我们开始本篇文章的学习。

1.简化Activity的跳转

通常我们在编写Activity跳转的时候,都会编写如下示例代码:

kotlin 复制代码
// 无参数时
val intent = Intent(context, SecondActivity::class.java)
context.startActivity(intent)

// 有参数时
val intent = Intent(context, SecondActivity::class.java)
intent.putExtra("param1","data1")
intent.putExtra("param2","data2")
context.startActivity(intent)

在我们学习完扩展函数和高阶函数后,我们完全可以将上面的代码简化。在Android Studio中选中当前项目右击,New -> Kotlin Class/File在弹出的选择框中,选择File,我们创建一个ExtendFunction.kt的文件。我们编写如下Activity跳转的代码:

kotlin 复制代码
// 无参数
fun <T : Activity> Activity.openActivity(clz: Class<T>) {
    val intent = Intent(this, clz)
    startActivity(intent)
}

// 有参数
inline fun <T : Activity> Activity.openActivity(clz: Class<T>, block: Intent.() -> Unit) {
    val intent = Intent(this, clz)
    intent.block()
    startActivity(intent)
}

我们定义了两个同名的Activity扩展函数openActivity(扩展函数和普通的函数一样,支持方法的重载)来实现两个Activity之间的跳转,并且它们都是泛型函数和顶层函数。这样我们就可以在Activity内部或者在拥有Activity实例的情况下访问该函数。在一个Activity内部我们想跳转到另外一个Activity,我们就可以这样写:

javascript 复制代码
// 无参数
openActivity(SecondActivity::class.java)

// 有参数
openActivity(SecondActivity::class.java) {
    putExtra("param1", "data1")
    putExtra("param2", "data2")
}

这样看上去是不是简洁了许多呢。

2.简化Fragment的事务操作

使用Fragment管理碎片化的页面,在Android开发中是最常见的了。首先我们需要先通过FragmentManager的beginTransaction()方法来获取一个事务的对象,然后再进行add、repleace、remove等操作,最后使用commit来提交事务,如下示例代码:

scss 复制代码
val transaction = supportFragmentManager.beginTransaction()
transaction.add(R.layout.id, Fragment())
transaction.commit()

分析这段代码我们发现,获取事务对象,和commit的操作我们完全可以通过扩展函数来简化。我们需要给FragmentManager添加一个扩展函数,且需要给这个扩展函数添加一个函数类型的参数,这个函数类型的参数需要定义在事务类中,因为我们希望在调用这个扩展函数的时候,能够拥有事务实例的上下文作用域,这样我们就可以进行一系列的事务操作,如下代码:

kotlin 复制代码
inline fun FragmentManager.commit(block: FragmentTransaction.() -> Unit) {
    val transaction = beginTransaction()
    transaction.block()
    transaction.commit()
}

在Activity中我们就可以这么写:

scss 复制代码
supportFragmentManager.commit {
    add(R.layout.id, Fragment())
}

事实上这块的内容,官方已经在"androidx.fragment:fragment-ktx:..."库中的FragmentManager.kt的文件中帮我实现了。这里为了更好的理解扩展函数和高阶函数的运用我们自己去分析实现了。

3.很好用的updateLayoutParams

通常我们想要动态的去更新View的宽高和边距等属性时,我们会先去获取View的LayoutParams对象,然后更新LayoutParams中的信息,再重新设置给View。比如我们想要动态的更改一个Button的宽高和左边距,我们就会这么写:

ini 复制代码
val layoutParams = button.layoutParams as ConstraintLayout.LayoutParams
layoutParams.width = 100
layoutParams.height = 100
layoutParams.leftMargin = 100
button.layoutParams = layoutParams

这里因为包裹Button的外层ViewGroup是ConstraintLayout,所以我这里使用的是ConstraintLayout.LayoutParams。而官方为我们提供了View的扩展函数updateLayoutParams:

kotlin 复制代码
public inline fun <reified T : ViewGroup.LayoutParams> View.updateLayoutParams(
    block: T.() -> Unit
) {
    val params = layoutParams as T
    block(params)
    layoutParams = params
}

上述示例代码我们就可以简化为:

ini 复制代码
button.updateLayoutParams<MarginLayoutParams> {
    width = 100
    height = 100
    leftMargin = 100
}

4.runCatching

高阶函数runCatching也是官方给我们提供的一个很好用的高阶函数,它的具体实现在kotlin包下的Result.kt文件中,具体代码如下:

kotlin 复制代码
public inline fun <R> runCatching(block: () -> R): Result<R> {
    return try {
        Result.success(block())
    } catch (e: Throwable) {
        Result.failure(e)
    }
}

这里我们有必要先来解释一下try、catch。在Kotlin中try、catch块也可以作为表达式使用,块中的最后一行代码是这个代码块的返回值。 当我们需要包裹一段可能会抛异常的代码我们就可以这么写:

kotlin.runCatching {

}

而runCatching方法的返回值是Result,关于Result类有如下代码:

kotlin 复制代码
public value class Result<out T> internal constructor(internal val value: Any?) : Serializable {
    public val isSuccess: Boolean get() = value !is Failure

    public val isFailure: Boolean get() = value is Failure

    public inline fun getOrNull(): T? =
        when {
            isFailure -> null
            else -> value as T
        }
    
    // ...

    public companion object {
        public inline fun <T> success(value: T): Result<T> = Result(value)
    
        public inline fun <T> failure(exception: Throwable): Result<T> =
            Result(createFailure(exception))
    }

    internal class Failure(
        val exception: Throwable
    ) : Serializable {
        override fun equals(other: Any?): Boolean = other is Failure && exception == other.exception
        override fun hashCode(): Int = exception.hashCode()
        override fun toString(): String = "Failure($exception)"
    }
}

为了方便阅读,这里对Result.kt文件中的注解、方法和注释做了删减。例如我们在解析json字符串的时候,我们就可以这么写:

kotlin 复制代码
runCatching {
    Gson().fromJson("{"name":"lisha","age":18}", Student::class.java)
}.onSuccess {
   println("onSuccess: $it")
}.onFailure {
    println("onFailure: ${it.message}")
}

// Student类
data class Student(val name:String, var age:Int)
    
// 输出
onSuccess: Student(name=lisha, age=18)    

总结:

扩展函数、高阶函数、带有接受者的函数类型在实际开发中的运用十分的广泛,这里更注重一种思想。比如说我们定义一个带有接收者的函数类型参数,我们就可以在这个函数类型实例化的Lambda表达式中获取接受者的上下文作用域。标准函数库中的apply、also、with、let函数亦是如此。关于带有上下文作用域的Lambda表达式,我们已经在高阶函数和Lambda表达式中详细的介绍,不熟悉的读者可以先去阅读下这块的内容。

好了到这里关于扩展函数和高阶函数的运用我们就讲解完了。下一篇文章继续讲解Kotlin中的基础知识类与继承。我们下期再见~

相关推荐
Fan_web3 分钟前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常5 分钟前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇1 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr1 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
网络研究院1 小时前
Android 安卓内存安全漏洞数量大幅下降的原因
android·安全·编程·安卓·内存·漏洞·技术
凉亭下1 小时前
android navigation 用法详细使用
android
Tiffany_Ho2 小时前
【TypeScript】知识点梳理(三)
前端·typescript
安冬的码畜日常3 小时前
【D3.js in Action 3 精译_029】3.5 给 D3 条形图加注图表标签(上)
开发语言·前端·javascript·信息可视化·数据可视化·d3.js
小白学习日记3 小时前
【复习】HTML常用标签<table>
前端·html