❝ 本文将介绍 Kotlin 中的扩展函数和扩展属性,并用这两个特性来逐步优化代码的写法,希望大家学习这种特性并在实践当中做更多的扩展。
❞
在 Android 开发中,大家可能经常使用这样的代码来判断或设置视图的可见性:
if (view.getVisibility() == View.VISIBLE) {
view.setVisibility(View.GONE);
// ...
}
或者封装了一个 px 转 dp 的工具类,如果计算逻辑比较复杂,可能会写成这样:
int width = (textView.getWidth() + DisplayUtils.dp2px(40)) / 2 - DisplayUtils.dp2px(18);
如果在一行代码中 DisplayUtils.dp2px()
工具类使用非常多的话代码会很长,也会增加理解难度。
那么,如果使用 Kotlin 中的扩展函数或扩展属性去精简一下,就可以方便很多。下面来介绍一下。
1. 扩展函数
所谓扩展函数,就是对一个现有类扩展定义一个成员函数,不过该定义在类的外面。一般我们如果想对一个类封装一个 API 方法,但又不能直接修改该类时,就可以用到扩展函数。
扩展方法的一般格式如下:
fun 类名.方法名([参数 1, 参数 2, ...]): 返回类型 {
方法体
}
下面,我们将判断或设置视图的可见性封装成扩展方法,并定义在一个 kt 文件的最外层:
// ViewExtensions.kt
fun View.isVisible(): Boolean {
return visibility == View.VISIBLE
}
fun View.setVisible(visible: Boolean) {
visibility = if (visible) View.VISIBLE else View.GONE
}
在扩展方法中,可以使用这个类的公有的方法和属性,例如上面代码中的 getVisibility()
和 setVisibility()
。
之后,在 Kotlin 代码中所有的 View 类及它的子类都可以用这两个方法了:
if (view.isVisible()) {
view.setVisible(false)
// ...
}
下面我们来看看扩展方法转换成 Java 代码是什么样的:
public final class ViewExtensionsKt {
public static final boolean isVisible(@NotNull View $this$isVisible) {
Intrinsics.checkParameterIsNotNull($this$isVisible, "$this$isVisible");
return $this$isVisible.getVisibility() == 0;
}
public static final void setVisible(@NotNull View $this$setVisible, boolean visible) {
Intrinsics.checkParameterIsNotNull($this$setVisible, "$this$setVisible");
$this$setVisible.setVisibility(visible ? 0 : 8);
}
}
可以看出来转成 Java 代码后,将这个要扩展的类换成静态方法的第一个参数了,而原扩展方法的参数列表变成了从第二个参数开始定义,因此,我们可以按如下方式在 Java 中使用该扩展方法:
if (ViewExtensionsKt.isVisible(view)) {
ViewExtensionsKt.setVisible(view, false);
}
至于怎么查看 Kotlin 转成的 Java 代码,方法如下:
- 在 Android Studio 顶部菜单中找到 「Tools → Kotlin → Show Kotlin Bytecode」;
- 在右侧的 「Kotlin Bytecode」 窗口中点击 「Decompile」 按钮,就可以看到对应的 Java 代码了。
这里还有个小技巧,如果大家不喜欢 Kotlin 文件转成 Java 后默认的类的名字(例如上面的 ViewExtensionsKt
),我们还可以自定义要转成 Java 的类名。
例如,在上例中的 ViewExtensions.kt
文件的顶部添加一行如下代码:
@file: JvmName("ViewUtils")
这样转成 Java 的类名就是 ViewUtils
了,如下:
public final class ViewUtils {
public static final boolean isVisible(@NotNull View $this$isVisible) {
Intrinsics.checkParameterIsNotNull($this$isVisible, "$this$isVisible");
return $this$isVisible.getVisibility() == 0;
}
public static final void setVisible(@NotNull View $this$setVisible, boolean visible) {
Intrinsics.checkParameterIsNotNull($this$setVisible, "$this$setVisible");
$this$setVisible.setVisibility(visible ? 0 : 8);
}
}
2. 扩展属性
Kotlin 除了可以扩展函数,还可以扩展属性。但是这个属性并不会保存在对象里,所以我们使用它时需要实现 「getter」 函数,如果是可变对象,则还要实现 「setter」 函数。格式如下:
val 类名.属性名: 类型
get() {
方法体
}
var 类名.属性名: 类型
get() {
方法体
}
set(value) {
方法体
}
下面,为视图扩展一个属性来判断其可见性:
val View.isVisible: Boolean
get() {
return visibility == View.VISIBLE
}
在 Kotlin 中,当方法体内只有一条 return 语句时是可以简写的,如下:
val View.isVisible get() = visibility == View.VISIBLE
接着,将该属性改为 var
,重写 「setter」 方法来设置可见性:
var View.isVisible
get() = visibility == View.VISIBLE
set(value) {
visibility = if (value) View.VISIBLE else View.GONE
}
这样,在 Kotlin 中就可以这么使用了:
if (view.isVisible) {
view.isVisible = false
// ...
}
我们再来看看扩展属性的定义转成 Java 代码是什么样子的:
public final class ViewUtils {
public static final boolean isVisible(@NotNull View $this$isVisible) {
Intrinsics.checkParameterIsNotNull($this$isVisible, "$this$isVisible");
return $this$isVisible.getVisibility() == 0;
}
public static final void setVisible(@NotNull View $this$isVisible, boolean value) {
Intrinsics.checkParameterIsNotNull($this$isVisible, "$this$isVisible");
$this$isVisible.setVisibility(value ? 0 : 8);
}
}
大家可以看到,扩展属性和扩展方法转成的 Java 代码是一样的,因此在 Java 中使用的时候也是一样的。
最后再说个 dp 转 px 的例子,我们可以对 Number
类扩展一个属性,代码如下:
val Number.dp get() = round(toFloat() * Resources.getSystem().displayMetrics.density).toInt()
这样我们就可以直接使用 18.dp
、0.5.dp
这种形式将一个数字转成对应的 dp 大小,是不是很简洁?
3. Kotlin 扩展库
其实,Google 官方已经为我们扩展了很多实用的函数和属性,比如 https://developer.android.com/kotlin/ktx。
大家有兴趣的可以去引入一下慢慢探索~
参考
- 《Kotlin 实战》