你應該知道的kotlin實用技巧

好文推薦:
作者:RicardoMJiang
轉載地址:https://juejin.cn/post/6921337734216810504

前言

衆所周知,kotlin是google力推的用以取代java的android開發語言
kotlin使用起來比較方便,同時有許多語法糖
本文主要講解了一些比較實用的kotlin技巧

自定義圓角矩形

在項目中,我們常常要定義圓角矩形背景,一般是用自定義drawable實現的
但是圓角矩形的背景與圓角常常會有細微的變化,而一旦變化我們又要新創建一個drawable文件
這樣就會導致文件爆炸的問題

我們可以利用kotlin的擴展函數,來實現簡單方便的圓角矩形背景

fun View.setRoundRectBg(color: Int = Color.WHITE, cornerRadius: Int = 15.dp) {
    background = GradientDrawable().apply {
        setColor(color)
        setCornerRadius(cornerRadius.toFloat())
    }
}

對於需要自定義背景的View,直接調用setRoundRectBg即可,簡單方便

reified使用

reified,kotlin中的泛型實化關鍵字,使抽象的東西更加具體或真實。
我們舉兩個例子來看看怎麼使用reified

startActivity例子

我們一般startActivity是這樣寫的

startActivity(context, NewActivity::class.java)  

我們利用reified定義一個擴展函數

// Function
inline fun <reified T : Activity> Activity.startActivity(context: Context) {
    startActivity(Intent(context, T::class.java))
}

// Caller
startActivity<NewActivity>(context)

使用 reified,通過添加類型傳遞簡化泛型參數
這樣就不用手動傳泛型的類型過去了

Gson解析例子

我們首先看下一般我們使用gson解析json是怎麼做的
在Java序列化庫(如Gson)中,當您想要反序列化該JSON字符串時,您最終必須將Class對象作爲參數傳遞,以便Gson知道您想要的類型。

User user = new Gson().fromJson(getJson(), User.class)

現在,讓我們一起展示reified類型實化參數的魔法 我們將創建一個非常輕量級的擴展函數來包裝Gson方法:

inline fun <reified T> Gson.fromJson(json: String) = 
        fromJson(json, T::class.java)

現在,在我們的Kotlin代碼中,我們可以反序列化JSON字符串,甚至根本不需要傳遞類型信息!

val user: User = Gson().fromJson(json)

Kotlin根據它的用法推斷出類型 - 因爲我們將它分配給User類型的變量,Kotlin使用它作爲fromJson()的類型參數

kotin接口支持SAM轉換

什麼是SAM轉換?可能有的同學還不太瞭解,這裏先科普一下:

SAM 轉換,即 Single Abstract Method Conversions,就是對於只有單個非默認抽象方法接口的轉換 —— 對於符合這個條件的接口(稱之爲 SAM Type ),在 Kotlin 中可以直接用 Lambda 來表示 —— 當然前提是 Lambda 的所表示函數類型能夠跟接口的中方法相匹配。

在Kotlin1.4之前,Kotlin是不支持Kotlin的SAM轉換的,只支持Java SAM轉換,官方給出的的解釋是:是 Kotlin 本身已經有了函數類型和高階函數,不需要在去SAM轉化。 這個解釋開發者並不買賬,如果你用過Java Lambda和Fuction Interface。當你切換到Kotlin時,就會很懵逼。看來Kotlin是意識到了這個,或者是看到開發者的反饋,終於支持了。

在1.4之前,只能傳遞一個對象,是不支持Kotlin SAM的,而在1.4之後,可以支持Kotlin SAM,但是用法有一丟丟變化。interface需要使用fun關鍵字聲明。使用fun關鍵字標記接口後,只要將此類接口作爲參數,就可以將lambda作爲參數傳遞。

// 注意需用fun 關鍵字聲明
fun interface Action {
    fun run()
}

fun runAction(a: Action) = a.run()

fun main(){
    // 1.4之前,只能使用object
    runAction(object : Action{
        override fun run() {
            println("run action")
        }
    })
     // 1.4-M1支持SAM,OK
    runAction {
        println("Hello, Kotlin 1.4!")
    }
}

委託

有時候,完成一些工作的方法是將它們委託給別人。這裏不是在建議您將自己的工作委託給朋友去做,而是在說將一個對象的工作委託給另一個對象。

當然,委託在軟件行業不是什麼新鮮名詞。委託 (Delegation) 是一種設計模式,在該模式中,對象會委託一個助手 (helper) 對象來處理請求,這個助手對象被稱爲代理。代理負責代表原始對象處理請求,並使結果可用於原始對象。

類委託

舉個例子,當我們要實現一個增強版的ArrayList,支持恢復最後一次刪除的item

實現這個用例的一種方式,是繼承 ArrayList 類。由於新的類繼承了具體的 ArrayList 類而不是實現 MutableList 接口,因此它與 ArrayList 的實現高度耦合。
如果只需要覆蓋 remove() 函數來保持對已刪除項目的引用,並將 MutableList 的其餘空實現委託給其他對象,那該有多好啊。爲了實現這一目標,Kotlin 提供了一種將大部分工作委託給一個內部 ArrayList 實例並且可以自定義其行爲的方式,併爲此引入了一個新的關鍵字: by。

<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
class ListWithTrash <T>(private val innerList: MutableList<T> = ArrayList<T>()) : MutableCollection<T> by innerList {
    var deletedItem : T? = null
    override fun remove(element: T): Boolean {
           deletedItem = element
            return innerList.remove(element)
    }
    fun recover(): T? {
        return deletedItem
    }
}

by 關鍵字告訴 Kotlin 將 MutableList 接口的功能委託給一個名爲 innerList 的內部 ArrayList。通過橋接到內部 ArrayList 對象方法的方式,ListWithTrash 仍然支持 MutableList 接口中的所有函數。與此同時,現在您可以添加自己的行爲了。

屬性委託

除了類代理,您還可以使用 by 關鍵字進行屬性代理。通過使用屬性代理,代理類會負責處理對應屬性 get 與 set 函數的調用。這一特性在您需要在其他對象間複用 getter/setter 邏輯時十分有用,同時也能讓您可以輕鬆地對簡單支持字段的功能進行擴展

舉個例子,利用委託屬性可以封裝SharedPreference
將數據存儲操作委託給代理類有幾個好處
1.則精簡了代碼,方便了存儲與讀取調用
2.與SP進行了解耦,後續如果要替換存儲庫,只需要修改代理類即可

調用如下:

object Pref: PreferenceHolder() {
    var isFirstInstall: Boolean by bindToPreferenceField(false)
    var time: Long? by bindToPreferenceFieldNullable()
}

具體實現可見:SharedPreferences用Kotlin應該這樣寫

帶狀態的LiveData

目前我們在開發的過程中越來越多的使用MVVM模式與ViewModel
我們也常常用LiveData來標識網絡請求狀態
我們需要定義請求開始,請求成功,請求失敗,三個LiveData

這其實也是很冗餘重複的代碼,因此我們可以進行一定的封裝,封裝一個帶狀態的LiveData

定義如下:

typealias StatefulLiveData<T> = LiveData<RequestState<T>>
typealias StatefulMutableLiveData<T> = MutableLiveData<RequestState<T>>

@MainThread
inline fun <T> StatefulLiveData<T>.observeState(
    owner: LifecycleOwner,
    init: ResultBuilder<T>.() -> Unit
) {
    val result = ResultBuilder<T>().apply(init)

    observe(owner) { state ->
        when (state) {
            is RequestState.Loading -> result.onLading.invoke()
            is RequestState.Success -> result.onSuccess(state.data)
            is RequestState.Error -> result.onError(state.error)
        }
    }
}

使用如下

val data = StatefulMutableLiveData<String>()
viewModel.data.observeState(viewLifecycleOwner) {
            onLading = {
                //loading
            }
            onSuccess = { data ->
                //success
            }
            onError = { exception ->
                //error
            }
        }

通過以上封裝,可以比較優雅簡潔的封裝網絡請求的loading,success,error狀態,精簡了代碼,結構也比較清晰

DSL

DSL(domain specific language),即領域專用語言:專門解決某一特定問題的計算機語言,比如大家耳熟能詳的 SQL 和正則表達式。
但是,如果爲解決某一特定領域問題就創建一套獨立的語言,開發成本和學習成本都很高,因此便有了內部 DSL 的概念。所謂內部 DSL,便是使用通用編程語言來構建 DSL。比如,本文提到的 Kotlin DSL,我們爲 Kotlin DSL 做一個簡單的定義:

“使用 Kotlin 語言開發的,解決特定領域問題,具備獨特代碼結構的 API 。”

舉個例子,我們使用TabLayout時,如果要爲他添加監聽,需要實現以下3個方法

override fun onTabReselected(tab: TabLayout.Tab?){

}

override fun onTabUnselected(tab: TabLayout.Tab?){

}

override fun onTabSelected(tab: TabLayout.Tab?){

}

其實我們一般只會用到onTabSelected方法,其餘兩個一般是空實現
我們利用DSL對OnTabSelectedListener進行封裝,即可避免寫不必要的空實現代碼

具體實現如下:

private typealias OnTabCallback = (tab: TabLayout.Tab?) -> Unit

class OnTabSelectedListenerBuilder : TabLayout.OnTabSelectedListener {

    private var onTabReselectedCallback: OnTabCallback? = null
    private var onTabUnselectedCallback: OnTabCallback? = null
    private var onTabSelectedCallback: OnTabCallback? = null

    override fun onTabReselected(tab: TabLayout.Tab?) =
            onTabReselectedCallback?.invoke(tab) ?: Unit

    override fun onTabUnselected(tab: TabLayout.Tab?) =
            onTabUnselectedCallback?.invoke(tab) ?: Unit

    override fun onTabSelected(tab: TabLayout.Tab?) =
            onTabSelectedCallback?.invoke(tab) ?: Unit

    fun onTabReselected(callback: OnTabCallback) {
        onTabReselectedCallback = callback
    }

    fun onTabUnselected(callback: OnTabCallback) {
        onTabUnselectedCallback = callback
    }

    fun onTabSelected(callback: OnTabCallback) {
        onTabSelectedCallback = callback
    }

}

fun registerOnTabSelectedListener(function: OnTabSelectedListenerBuilder.() -> Unit) =
        OnTabSelectedListenerBuilder().also(function)

定義DSL的一般步驟:

  • 1.先定義一個類去實現回調接口,並且實現它的回調方法。
  • 2.觀察回調方法的參數,提取成一個函數類型(function type),並且按照需要使用類型別名給函數類型起一個別稱,並且用私有修飾。
  • 3.在類裏面聲明一些可空的函數類型的可變(var)私有成員變量,並且在回調函數中拿到對應的變量實現它的invoke函數,傳入對應的參數。
  • 4.在類中定義一些跟回調接口一樣名字,但是參數是對應的函數類型的函數,並且將函數類型賦值給當前類的對應的成員變量。
  • 5.定義一個成員函數,參數是一個帶有我們定好那個類的接受者對象並且返回Unit的Lambda表達式,在函數裏創建相應的對象,並且使用also函數把Lambda表達式傳進去。

調用如下:

tabLayout.addOnTabSelectedListener(registerOnTabSelectedListener {
    onTabSelected { vpOrder.currentItem = it?.position ?: 0 }
})

如上,就可以避免寫一些不必要的空實現代碼了

最後

如果還想了解更多Android 相關的更多知識點,可以點進我的GitHub項目中:https://github.com/733gh/GH-Android-Review-master自行查看,裏面記錄了許多的Android 知識點。

持續更新--請Android的小夥伴關注! 喜歡的話給一個贊Star吧!

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