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

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

參考

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