亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

Kotlin 擴展函數

1. 為什么需要擴展函數?

我們都知道 Koltin 這門語言與 Java 有非常好的互操作性,并且擴展函數這個新特性可以很平滑與現有Java 代碼集成。甚至純 Kotlin 的項目都可以基于 Java 庫或者 Android 中的一些第三方框架庫來構建,所以擴展函數非常適合 Kotlin 和 Java 語言混合開發模式。在很多公司一些比較穩定良好的庫都是 Java 開發的,也完全沒必要去用 Kotlin 語言重寫。但是想要擴展庫的接口和功能,這時候擴展函數可能就會派上用場。使用 Kotlin 的擴展函數還有一個好處就是沒有副作用,不會對原有庫代碼或功能產生影響。

先來一個擴展函數的例子一睹為快,來實現一個給 Android 中 TextView 組件設置加粗的擴展函數:

//擴展函數的定義
fun TextView.isBold() = this.apply { 
    paint.isFakeBoldText = true
}

//擴展函數的調用
activity.find<TextView>(R.id.course_comment_tv_score).isBold()

2. 擴展函數的語法

上面例子不難看出,Kotlin 的擴展函數是真的強大,可以毫無副作用給原有庫的類增加屬性和方法,比如例子中 TextView,我們根本沒有去動 TextView 源碼,但是卻給它增加一個擴展函數。具有那么強大功能,到底它背后原理是什么呢?下面我們來深入學習下它的語法以及語法背后的原理。

2.1 擴展函數的定義

Kotlin 可以為一個不能修改的或來自第三方庫中的類編寫一個新的函數。 這個新增的函數就像那個原始類本來就有的函數一樣,可以用普通的方法調用,這種機制的函數稱為擴展函數。

//擴展函數定義
fun TextView.isBold() = this.apply { 
    paint.isFakeBoldText = true
}

2.2 擴展函數的分析

擴展函數只需要把擴展的類或者接口名稱,放到即將要添加的函數名前面。這個類或者名稱就叫做接收者類型,類的名稱與函數之間用 . 調用連接。this指代的就是接收者對象,它可以訪問擴展的這個類可訪問的函數或屬性。

圖片描述

2.3 擴展函數的背后原理

擴展函數實際上就是一個對應 Java 中的靜態函數,這個靜態函數參數為接收者類型的對象,然后利用這個對象就可以訪問這個類中的成員屬性和方法了,并且最后返回一個這個接收者類型對象本身。這樣在外部感覺和使用類的成員函數是一樣的:

//這個類名就是頂層文件名+“Kt”后綴
public final class ExtendsionTextViewKt {
   @NotNull
   public static final TextView isBold(@NotNull TextView $receiver) {//擴展函數isBold對應實際上是Java中的靜態函數,并且傳入一個接收者類型對象作為參數
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      $receiver.getPaint().setFakeBoldText(true);//設置加粗
      return $receiver;//最后返回這個接收者對象自身,以致于我們在Kotlin中完全可以使用this替代接收者對象或者直接不寫。
   }
}

3. 擴展函數的使用場景

擴展函數的使用場景主要就是毫無副作用給原有庫的類增加函數,增強類的行為。比如 Android 中給所有 View 組件增加常見動畫、給 ImageView 組件增加一個加載網絡圖片函數、給SpannableStringBuilder 增加 bold 加粗、italic 斜體、color 顏色等一系列 Span 的設置以及很多需要重復設置模板代碼場景都可以使用擴展函數。

4. 擴展函數的使用示例

示例1: Android 中給 ImageView 組件增加一個 loadUrl 的擴展函數,不再需要繁瑣 Glide 的調用,只需簡單一行 loadUrl 代碼的調用就能實現加載圖片功能:

//loadUrl擴展函數接收者類型是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也可以省略
}

//使用擴展函數來加載多張圖片的調用方式
course_cover_iv1.loadUrl("http://www.xianlaiwan.cn/test1.png")//imageView直接調用loadUrl像是給ImageView增加一個網絡圖片加載函數一樣,調用非常簡單。
course_cover_iv2.loadUrl("http://www.xianlaiwan.cn/test2.png")
course_cover_iv3.loadUrl("http://www.xianlaiwan.cn/test3.png")

//不使用擴展函數來加載多張圖片的調用方式
Glide.with(context)
     .view(course_cover_iv1)
     .url("http://www.xianlaiwan.cn/test1.png")
     .start()

Glide.with(context)
     .view(course_cover_iv2)
     .url("http://www.xianlaiwan.cn/test2.png")
     .start()

Glide.with(context)
     .view(course_cover_iv3)
     .url("http://www.xianlaiwan.cn/test3.png")
     .start()     

示例2: Android 中給 View 組件增加一個 animAlpha 透明動畫的擴展函數,不需要每次都去重新寫屬性動畫的配置,只需要簡單一行代碼調用 View 中的方法即可。像是給原本就不具備透明動畫 View 組件無縫地提供一個動畫的功能:

//animAlpha擴展函數接收者類型是View,也就是View的子類都可以直接使用animAlpha函數
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擴展函數,設置View組件的顯示和消失透明度動畫,通過isVisible控制
fun View.animAlphaVisible(
       isVisible: Boolean, 
       duration: Long = 600, 
       animatorEnd: ((Animator?) -> Unit)? = null
) {
    //擴展函數內部再調用擴展函數animAlpha
    animAlpha(duration, alpha = if (isVisible) 1f else 0f, animatorEnd = animatorEnd)
}

//擴展函數animAlpha調用
mEditText.animAlpha(alpha = 0.2f)//EditText組件通過最開始透明度變化到alpha為0.2f

//擴展函數animAlphaVisible調用
mIv.animAlphaVisible(isVisible = true)//ImageView組件設置透明度顯示的動畫
mTv.animAlphaVisible(isVisible = false)//TextView組件設置透明度消失的動畫

5. 擴展函數需要注意的點

  • 擴展函數不能像成員函數那樣被子類重寫;

  • 擴展函數不能訪問原始類中私有成員屬性或成員函數;

  • 擴展函數的本質通過傳入對象實例,委托對象來訪問成員函數,所以它的訪問權限和對象訪問權限一致。

6. 擴展函數與成員函數的區別

我們通過上述擴展函數的學習都知道,擴展函數在外部調用方式來看和類的成員函數是一致,但是兩者卻有著本質的區別。

  • 擴展函數和成員函數使用方式類似,可以直接訪問被擴展類的方法和屬性。(原理: 傳入了一個擴展類的對象,內部實際上是用實例對象去訪問擴展類的方法和屬性);
  • 擴展函數不能打破擴展類的封裝性,不能像成員函數一樣直接訪問內部私有函數和屬性。(原理: 原理很簡單,擴展函數訪問實際是類的對象訪問,由于類的對象實例不能訪問內部私有函數和屬性,自然擴展函數也就不能訪問內部私有函數和屬性了);
  • 擴展函數實際上是一個靜態函數是處于類的外部,而成員函數則是類的內部函數;
  • 父類成員函數可以被子類重寫,而擴展函數則不行。

7. 擴展函數使用經驗

雖然擴展函數功能十分強大,但是記住千萬不要濫用擴展函數,并不是所有場景定義都合適定義成擴展函數。定義成擴展函數需要抓住以下三個點:

  • 第一,這個函數需要被很多地方復用,定義成擴展函數減少很多重復模板代碼;

  • 第二,這個函數具有擴展的原始類,如果沒有那么就不適合定義成擴展函數,它經常很容易會和頂層函數使用場景弄混;

  • 第三,如果需要擴展一個原始類的行為時候用擴展函數,如果是擴展一個原始類的狀態的時候建議使用擴展屬性。

8. 總結

擴展函數的本質是一個對應 Java 中的靜態函數,這個靜態函數參數為接收者類型的對象,然后利用這個對象就可以訪問這個類中的成員屬性和方法了,并且最后返回一個這個接收者類型對象本身。這樣在外部感覺和使用類的成員函數是一樣的。另外,擴展函數與成員函數的相同點和不同點也是擴展函數中的重點。