Kotlin 擴(kuò)展函數(shù)
1. 為什么需要擴(kuò)展函數(shù)?
我們都知道 Koltin 這門語言與 Java 有非常好的互操作性,并且擴(kuò)展函數(shù)這個新特性可以很平滑與現(xiàn)有Java 代碼集成。甚至純 Kotlin 的項目都可以基于 Java 庫或者 Android 中的一些第三方框架庫來構(gòu)建,所以擴(kuò)展函數(shù)非常適合 Kotlin 和 Java 語言混合開發(fā)模式。在很多公司一些比較穩(wěn)定良好的庫都是 Java 開發(fā)的,也完全沒必要去用 Kotlin 語言重寫。但是想要擴(kuò)展庫的接口和功能,這時候擴(kuò)展函數(shù)可能就會派上用場。使用 Kotlin 的擴(kuò)展函數(shù)還有一個好處就是沒有副作用,不會對原有庫代碼或功能產(chǎn)生影響。
先來一個擴(kuò)展函數(shù)的例子一睹為快,來實現(xiàn)一個給 Android 中 TextView 組件設(shè)置加粗的擴(kuò)展函數(shù):
//擴(kuò)展函數(shù)的定義
fun TextView.isBold() = this.apply {
paint.isFakeBoldText = true
}
//擴(kuò)展函數(shù)的調(diào)用
activity.find<TextView>(R.id.course_comment_tv_score).isBold()
2. 擴(kuò)展函數(shù)的語法
上面例子不難看出,Kotlin 的擴(kuò)展函數(shù)是真的強(qiáng)大,可以毫無副作用給原有庫的類增加屬性和方法,比如例子中 TextView,我們根本沒有去動 TextView 源碼,但是卻給它增加一個擴(kuò)展函數(shù)。具有那么強(qiáng)大功能,到底它背后原理是什么呢?下面我們來深入學(xué)習(xí)下它的語法以及語法背后的原理。
2.1 擴(kuò)展函數(shù)的定義
Kotlin 可以為一個不能修改的或來自第三方庫中的類編寫一個新的函數(shù)。 這個新增的函數(shù)就像那個原始類本來就有的函數(shù)一樣,可以用普通的方法調(diào)用,這種機(jī)制的函數(shù)稱為擴(kuò)展函數(shù)。
//擴(kuò)展函數(shù)定義
fun TextView.isBold() = this.apply {
paint.isFakeBoldText = true
}
2.2 擴(kuò)展函數(shù)的分析
擴(kuò)展函數(shù)只需要把擴(kuò)展的類或者接口名稱,放到即將要添加的函數(shù)名前面。這個類或者名稱就叫做接收者類型,類的名稱與函數(shù)之間用 .
調(diào)用連接。this指代的就是接收者對象,它可以訪問擴(kuò)展的這個類可訪問的函數(shù)或?qū)傩浴?/p>
2.3 擴(kuò)展函數(shù)的背后原理
擴(kuò)展函數(shù)實際上就是一個對應(yīng) Java 中的靜態(tài)函數(shù),這個靜態(tài)函數(shù)參數(shù)為接收者類型的對象,然后利用這個對象就可以訪問這個類中的成員屬性和方法了,并且最后返回一個這個接收者類型對象本身。這樣在外部感覺和使用類的成員函數(shù)是一樣的:
//這個類名就是頂層文件名+“Kt”后綴
public final class ExtendsionTextViewKt {
@NotNull
public static final TextView isBold(@NotNull TextView $receiver) {//擴(kuò)展函數(shù)isBold對應(yīng)實際上是Java中的靜態(tài)函數(shù),并且傳入一個接收者類型對象作為參數(shù)
Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
$receiver.getPaint().setFakeBoldText(true);//設(shè)置加粗
return $receiver;//最后返回這個接收者對象自身,以致于我們在Kotlin中完全可以使用this替代接收者對象或者直接不寫。
}
}
3. 擴(kuò)展函數(shù)的使用場景
擴(kuò)展函數(shù)的使用場景主要就是毫無副作用給原有庫的類增加函數(shù),增強(qiáng)類的行為。比如 Android 中給所有 View 組件增加常見動畫、給 ImageView 組件增加一個加載網(wǎng)絡(luò)圖片函數(shù)、給SpannableStringBuilder
增加 bold
加粗、italic
斜體、color
顏色等一系列 Span
的設(shè)置以及很多需要重復(fù)設(shè)置模板代碼場景都可以使用擴(kuò)展函數(shù)。
4. 擴(kuò)展函數(shù)的使用示例
示例1: Android 中給 ImageView 組件增加一個 loadUrl 的擴(kuò)展函數(shù),不再需要繁瑣 Glide 的調(diào)用,只需簡單一行 loadUrl 代碼的調(diào)用就能實現(xiàn)加載圖片功能:
//loadUrl擴(kuò)展函數(shù)接收者類型是ImageView
fun ImageView.loadUrl(requestManager: RequestManager = Glide.with(context)
, url: String = ""
, urls: List<String> = listOf(url)
) {
requestManager.view(this).url(urls).start()//this指代的是ImageView這個接收者對象實例, 這里this也可以省略
}
//使用擴(kuò)展函數(shù)來加載多張圖片的調(diào)用方式
course_cover_iv1.loadUrl("http://idcbgp.cn/test1.png")//imageView直接調(diào)用loadUrl像是給ImageView增加一個網(wǎng)絡(luò)圖片加載函數(shù)一樣,調(diào)用非常簡單。
course_cover_iv2.loadUrl("http://idcbgp.cn/test2.png")
course_cover_iv3.loadUrl("http://idcbgp.cn/test3.png")
//不使用擴(kuò)展函數(shù)來加載多張圖片的調(diào)用方式
Glide.with(context)
.view(course_cover_iv1)
.url("http://idcbgp.cn/test1.png")
.start()
Glide.with(context)
.view(course_cover_iv2)
.url("http://idcbgp.cn/test2.png")
.start()
Glide.with(context)
.view(course_cover_iv3)
.url("http://idcbgp.cn/test3.png")
.start()
示例2: Android 中給 View 組件增加一個 animAlpha 透明動畫的擴(kuò)展函數(shù),不需要每次都去重新寫屬性動畫的配置,只需要簡單一行代碼調(diào)用 View 中的方法即可。像是給原本就不具備透明動畫 View 組件無縫地提供一個動畫的功能:
//animAlpha擴(kuò)展函數(shù)接收者類型是View,也就是View的子類都可以直接使用animAlpha函數(shù)
fun View.animAlpha(
duration: Long = 600,
alpha: Float = 0f,
delay: Long = 0,
animatorEnd: ((Animator?) -> Unit)? = null
) {
this.animate()//this指代的是View這個接收者對象實例, 這里this也可以省略
.setDuration(duration)
.setInterpolator(AccelerateDecelerateInterpolator())
.setStartDelay(delay)
.alpha(alpha)
.setListener(object : Animator.AnimatorListener {
override fun onAnimationRepeat(animation: Animator?) {
}
override fun onAnimationEnd(animation: Animator?) {
animatorEnd?.invoke(animation)
}
override fun onAnimationCancel(animation: Animator?) {
}
override fun onAnimationStart(animation: Animator?) {
}
})
.start()
}
//animAlphaVisible擴(kuò)展函數(shù),設(shè)置View組件的顯示和消失透明度動畫,通過isVisible控制
fun View.animAlphaVisible(
isVisible: Boolean,
duration: Long = 600,
animatorEnd: ((Animator?) -> Unit)? = null
) {
//擴(kuò)展函數(shù)內(nèi)部再調(diào)用擴(kuò)展函數(shù)animAlpha
animAlpha(duration, alpha = if (isVisible) 1f else 0f, animatorEnd = animatorEnd)
}
//擴(kuò)展函數(shù)animAlpha調(diào)用
mEditText.animAlpha(alpha = 0.2f)//EditText組件通過最開始透明度變化到alpha為0.2f
//擴(kuò)展函數(shù)animAlphaVisible調(diào)用
mIv.animAlphaVisible(isVisible = true)//ImageView組件設(shè)置透明度顯示的動畫
mTv.animAlphaVisible(isVisible = false)//TextView組件設(shè)置透明度消失的動畫
5. 擴(kuò)展函數(shù)需要注意的點
-
擴(kuò)展函數(shù)不能像成員函數(shù)那樣被子類重寫;
-
擴(kuò)展函數(shù)不能訪問原始類中私有成員屬性或成員函數(shù);
-
擴(kuò)展函數(shù)的本質(zhì)通過傳入對象實例,委托對象來訪問成員函數(shù),所以它的訪問權(quán)限和對象訪問權(quán)限一致。
6. 擴(kuò)展函數(shù)與成員函數(shù)的區(qū)別
我們通過上述擴(kuò)展函數(shù)的學(xué)習(xí)都知道,擴(kuò)展函數(shù)在外部調(diào)用方式來看和類的成員函數(shù)是一致,但是兩者卻有著本質(zhì)的區(qū)別。
- 擴(kuò)展函數(shù)和成員函數(shù)使用方式類似,可以直接訪問被擴(kuò)展類的方法和屬性。(原理: 傳入了一個擴(kuò)展類的對象,內(nèi)部實際上是用實例對象去訪問擴(kuò)展類的方法和屬性);
- 擴(kuò)展函數(shù)不能打破擴(kuò)展類的封裝性,不能像成員函數(shù)一樣直接訪問內(nèi)部私有函數(shù)和屬性。(原理: 原理很簡單,擴(kuò)展函數(shù)訪問實際是類的對象訪問,由于類的對象實例不能訪問內(nèi)部私有函數(shù)和屬性,自然擴(kuò)展函數(shù)也就不能訪問內(nèi)部私有函數(shù)和屬性了);
- 擴(kuò)展函數(shù)實際上是一個靜態(tài)函數(shù)是處于類的外部,而成員函數(shù)則是類的內(nèi)部函數(shù);
- 父類成員函數(shù)可以被子類重寫,而擴(kuò)展函數(shù)則不行。
7. 擴(kuò)展函數(shù)使用經(jīng)驗
雖然擴(kuò)展函數(shù)功能十分強(qiáng)大,但是記住千萬不要濫用擴(kuò)展函數(shù),并不是所有場景定義都合適定義成擴(kuò)展函數(shù)。定義成擴(kuò)展函數(shù)需要抓住以下三個點:
-
第一,這個函數(shù)需要被很多地方復(fù)用,定義成擴(kuò)展函數(shù)減少很多重復(fù)模板代碼;
-
第二,這個函數(shù)具有擴(kuò)展的原始類,如果沒有那么就不適合定義成擴(kuò)展函數(shù),它經(jīng)常很容易會和頂層函數(shù)使用場景弄混;
-
第三,如果需要擴(kuò)展一個原始類的行為時候用擴(kuò)展函數(shù),如果是擴(kuò)展一個原始類的狀態(tài)的時候建議使用擴(kuò)展屬性。
8. 總結(jié)
擴(kuò)展函數(shù)的本質(zhì)是一個對應(yīng) Java 中的靜態(tài)函數(shù),這個靜態(tài)函數(shù)參數(shù)為接收者類型的對象,然后利用這個對象就可以訪問這個類中的成員屬性和方法了,并且最后返回一個這個接收者類型對象本身。這樣在外部感覺和使用類的成員函數(shù)是一樣的。另外,擴(kuò)展函數(shù)與成員函數(shù)的相同點和不同點也是擴(kuò)展函數(shù)中的重點。