Kotlin 真香系列:扩展函数和属性

本文将介绍 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 代码,方法如下:

  1. 在 Android Studio 顶部菜单中找到 Tools → Kotlin → Show Kotlin Bytecode
  2. 在右侧的 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.dp0.5.dp 这种形式将一个数字转成对应的 dp 大小,是不是很简洁?

3. Kotlin 扩展库

其实,Google 官方已经为我们扩展了很多实用的函数和属性,比如 https://developer.android.com/kotlin/ktx

大家有兴趣的可以去引入一下慢慢探索~

参考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章