好文推薦:
作者: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吧!