本文將介紹 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。
大家有興趣的可以去引入一下慢慢探索~