在 Java 中如果我們要為類添加新功能枉昏,就必須使用繼承或者像裝飾者這樣的設(shè)計模式萄凤,但是在 Kotlin 中這些可以通過叫做擴展
的方式來完成黍特。平時我們開發(fā) Android 的過程中,會逐漸總結(jié)出各種各樣的工具類嫉鲸,如果使用 Kotlin 則可以通過擴展的方式产上,來簡化或者替代這些工具類棵磷。
Kotlin 擴展包括擴展屬性、擴展函數(shù) 2 種方式蒂秘,具體語法這里不多說了泽本,主要講講通過擴展,在 Android 開發(fā)中可以為我們帶來哪些便利姻僧。
1. 替代View.setOnClickListener方法
在 Java 中為一個 View 設(shè)置點擊事件,我們必須這樣寫:
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
如果一個頁面中要為很多 View 設(shè)置點擊事件蒲牧,寫起來巨繁瑣撇贺,在 Kotlin 中我們可以為 View 擴展一個函數(shù)來輕松實現(xiàn)點擊事件:
fun <T : View> T.click(block: (T) -> Unit) {
setOnClickListener {
block(this)
}
}
這個時候,我們?yōu)?View 設(shè)置點擊事件可以這樣寫:
view.click {
//處理點擊事件邏輯
}
在寫法上是不是簡單多了冰抢,除此之外還可通過擴展屬性來實現(xiàn)更高級功能松嘶。舉個栗子,我們不希望某個按鈕被頻繁點擊挎扰,在 Java 中的做法是設(shè)置一個變量記錄點擊時間翠订,如果在某個時間間隔內(nèi)再次觸發(fā)點擊巢音,則不響應(yīng)此事件。使用 Kotlin 我們可以這樣實現(xiàn)該功能:
先為 View 擴展 2 個屬性:
//私有擴展屬性尽超,允許2次點擊的間隔時間
private var <T : View> T.delayTime: Long
get() = getTag(0x7FFF0001) as? Long ?: 0
set(value) {
setTag(0x7FFF0001, value)
}
//私有擴展屬性官撼,記錄點擊時的時間戳
private var <T : View> T.lastClickTime: Long
get() = getTag(0x7FFF0002) as? Long ?: 0
set(value) {
setTag(0x7FFF0002, value)
}
再為 View 擴展一個方法:
//私有擴展方法,判斷能否觸發(fā)點擊事件
private fun <T : View> T.canClick(): Boolean {
var flag = false
var now = System.currentTimeMillis()
if (now - this.lastClickTime >= this.delayTime) {
flag = true
this.lastClickTime = now
}
return flag
}
//擴展點擊事件似谁,默認 500ms 內(nèi)不能觸發(fā) 2 次點擊
fun <T : View> T.clickWithDuration(time: Long = 500, block: (T) -> Unit) {
delayTime = time
setOnClickListener {
if (canClick()) {
block(this)
}
}
}
在 Kotlin 中使用傲绣,我們只需這樣調(diào)用:
//只有間隔1秒之后,點擊才會生效
view.clickWithDuration(1000) {
//點擊事件
}
上面這個例子中巩踏,實際上是通過 setTag()/getTag() 來存儲 2 個擴展屬性真正的值的秃诵。這是因為擴展屬性并沒有將實際的成員插入類中,他們只能由顯示提供的 getters/setters 定義塞琼。
2. Context的一些擴展
在 Java 中跳轉(zhuǎn)到另一個頁面菠净,一般得這樣寫:
Intent intent = new Intent(context, MainActivity.class);
context.startActivity(intent)
使用 Kotlin 后我們可以這樣寫:
//使用內(nèi)聯(lián)函數(shù)的泛型參數(shù) reified 特性來實現(xiàn)
inline fun <reified T : Activity> Context.startActivity() {
val intent = Intent(this, T::class.java)
if (this !is Activity) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
startActivity(intent)
}
//這個時候跳轉(zhuǎn)可以這樣寫,是不是特別簡潔
Context.startActivity<MainActivity>()
還有很多我們常用的方法:
//屏幕寬度(px)
inline val Context.screenWidth: Int
get() = resources.displayMetrics.widthPixels
//屏幕高度(px)
inline val Context.screenHeight: Int
get() = resources.displayMetrics.heightPixels
//屏幕的密度
inline val Context.density: Float
get() = resources.displayMetrics.density
//dp 轉(zhuǎn)為 px
inline fun Context.dp2px(value: Int): Int = (density * value).toInt()
//dp 轉(zhuǎn)為 px
inline fun Context.dp2px(value: Float): Int = (density * value).toInt()
//px 轉(zhuǎn)為 dp
inline fun Context.px2dp(value: Int): Float = value.toFloat() / density
在實際使用時彪杉,這些方法和屬性就像類本身定義的一樣毅往,我們可以直接拿來使用,非常方便在讶,省去了類似的工具類方法調(diào)用煞抬。
3. 可空接收者
在 Java 中字符串操作很容易出現(xiàn)空指針異常,要判斷一個字符串為空构哺,一般得這樣判斷:
String str = null;
//對字符串 str 做是否為空判斷
if (str == null || str.length() == 0) {
}
在 Kotlin 中我們要判斷字符串是否為空革答,sdk 里直接提供了一個方法 isNullOrEmpty
,看源碼是通過擴展來實現(xiàn)的曙强,主要代碼如下:
public inline fun CharSequence?.isNullOrEmpty(): Boolean {
return this == null || this.length == 0
}
該方法與前面擴展函數(shù)在形式上的唯一差別是残拐,在類型后面跟著的是?.
而不是.
,然后在方法體內(nèi)可以通過this == null
來判斷調(diào)用該方法的對象是否為 null碟嘴,上面的 Java 代碼我們用 Kotlin 來寫:
var str = null
//這里再也不需要額外對 str 做非空判斷了溪食,可以放心的使用了
if (str.isNullOrEmpty()) {
}
使用這種方式來擴展函數(shù),媽媽再也不用擔心空指針異常了娜扇。
4. 小結(jié)
在 Kotlin sdk 里错沃,可以發(fā)現(xiàn)它采用了大量的擴展函數(shù)。合理使用擴展雀瓢,可以大量簡化代碼枢析,提升開發(fā)效率,這個是 Kotlin 中我最喜歡的特性之一刃麸。