前言
眾所周知单默,kotlin是google力推的用以取代java的android開發(fā)語言
kotlin使用起來比較方便迅脐,同時有許多語法糖
本文主要講解了一些比較實用的kotlin技巧
自定義圓角矩形
在項目中,我們常常要定義圓角矩形背景捌肴,一般是用自定義drawable實現(xiàn)的
但是圓角矩形的背景與圓角常常會有細微的變化,而一旦變化我們又要新創(chuàng)建一個drawable文件
這樣就會導(dǎo)致文件爆炸的問題
我們可以利用kotlin的擴展函數(shù),來實現(xiàn)簡單方便的圓角矩形背景
fun View.setRoundRectBg(color: Int = Color.WHITE, cornerRadius: Int = 15.dp) {
background = GradientDrawable().apply {
setColor(color)
setCornerRadius(cornerRadius.toFloat())
}
}
對于需要自定義背景的View,直接調(diào)用setRoundRectBg即可椅邓,簡單方便
reified使用
reified,kotlin中的泛型實化關(guān)鍵字,使抽象的東西更加具體或真實昧狮。
我們舉兩個例子來看看怎么使用reified
startActivity例子
我們一般startActivity是這樣寫的
startActivity(context, NewActivity::class.java)
我們利用reified定義一個擴展函數(shù)
// Function
inline fun <reified T : Activity> Activity.startActivity(context: Context) {
startActivity(Intent(context, T::class.java))
}
// Caller
startActivity<NewActivity>(context)
使用 reified景馁,通過添加類型傳遞簡化泛型參數(shù)
這樣就不用手動傳泛型的類型過去了
Gson解析例子
我們首先看下一般我們使用gson解析json是怎么做的
在Java序列化庫(如Gson)中,當(dāng)您想要反序列化該JSON字符串時逗鸣,您最終必須將Class對象作為參數(shù)傳遞合住,以便Gson知道您想要的類型。
User user = new Gson().fromJson(getJson(), User.class)
現(xiàn)在撒璧,讓我們一起展示reified類型實化參數(shù)的魔法 我們將創(chuàng)建一個非常輕量級的擴展函數(shù)來包裝Gson方法:
inline fun <reified T> Gson.fromJson(json: String) =
fromJson(json, T::class.java)
現(xiàn)在透葛,在我們的Kotlin代碼中,我們可以反序列化JSON字符串卿樱,甚至根本不需要傳遞類型信息僚害!
val user: User = Gson().fromJson(json)
Kotlin根據(jù)它的用法推斷出類型 - 因為我們將它分配給User類型的變量,Kotlin使用它作為fromJson()的類型參數(shù)
kotin接口支持SAM轉(zhuǎn)換
什么是SAM轉(zhuǎn)換繁调?可能有的同學(xué)還不太了解萨蚕,這里先科普一下:
SAM 轉(zhuǎn)換,即 Single Abstract Method Conversions蹄胰,就是對于只有單個非默認抽象方法接口的轉(zhuǎn)換 —— 對于符合這個條件的接口(稱之為 SAM Type )岳遥,在 Kotlin 中可以直接用 Lambda 來表示 —— 當(dāng)然前提是 Lambda 的所表示函數(shù)類型能夠跟接口的中方法相匹配。
在Kotlin1.4之前裕寨,Kotlin是不支持Kotlin的SAM轉(zhuǎn)換的寒随,只支持Java SAM轉(zhuǎn)換,官方給出的的解釋是:是 Kotlin 本身已經(jīng)有了函數(shù)類型和高階函數(shù)帮坚,不需要在去SAM轉(zhuǎn)化妻往。 這個解釋開發(fā)者并不買賬,如果你用過Java Lambda和Fuction Interface试和。當(dāng)你切換到Kotlin時讯泣,就會很懵逼≡暮罚看來Kotlin是意識到了這個好渠,或者是看到開發(fā)者的反饋昨稼,終于支持了。
在1.4之前拳锚,只能傳遞一個對象假栓,是不支持Kotlin SAM的,而在1.4之后霍掺,可以支持Kotlin SAM,但是用法有一丟丟變化匾荆。interface需要使用fun關(guān)鍵字聲明。使用fun關(guān)鍵字標(biāo)記接口后杆烁,只要將此類接口作為參數(shù)牙丽,就可以將lambda作為參數(shù)傳遞。
// 注意需用fun 關(guān)鍵字聲明
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!")
}
}
委托
有時候烤芦,完成一些工作的方法是將它們委托給別人。這里不是在建議您將自己的工作委托給朋友去做析校,而是在說將一個對象的工作委托給另一個對象构罗。
當(dāng)然,委托在軟件行業(yè)不是什么新鮮名詞智玻。委托 (Delegation) 是一種設(shè)計模式绰播,在該模式中,對象會委托一個助手 (helper) 對象來處理請求尚困,這個助手對象被稱為代理蠢箩。代理負責(zé)代表原始對象處理請求,并使結(jié)果可用于原始對象事甜。
類委托
舉個例子谬泌,當(dāng)我們要實現(xiàn)一個增強版的ArrayList,支持恢復(fù)最后一次刪除的item
實現(xiàn)這個用例的一種方式,是繼承 ArrayList 類逻谦。由于新的類繼承了具體的 ArrayList 類而不是實現(xiàn) MutableList 接口掌实,因此它與 ArrayList 的實現(xiàn)高度耦合。
如果只需要覆蓋 remove() 函數(shù)來保持對已刪除項目的引用邦马,并將 MutableList 的其余空實現(xiàn)委托給其他對象贱鼻,那該有多好啊。為了實現(xiàn)這一目標(biāo)滋将,Kotlin 提供了一種將大部分工作委托給一個內(nèi)部 ArrayList 實例并且可以自定義其行為的方式邻悬,并為此引入了一個新的關(guān)鍵字: 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 關(guān)鍵字告訴 Kotlin 將 MutableList 接口的功能委托給一個名為 innerList 的內(nèi)部 ArrayList随闽。通過橋接到內(nèi)部 ArrayList 對象方法的方式父丰,ListWithTrash 仍然支持 MutableList 接口中的所有函數(shù)。與此同時掘宪,現(xiàn)在您可以添加自己的行為了蛾扇。
屬性委托
除了類代理攘烛,您還可以使用 by 關(guān)鍵字進行屬性代理。通過使用屬性代理镀首,代理類會負責(zé)處理對應(yīng)屬性 get 與 set 函數(shù)的調(diào)用坟漱。這一特性在您需要在其他對象間復(fù)用 getter/setter 邏輯時十分有用,同時也能讓您可以輕松地對簡單支持字段的功能進行擴展
舉個例子更哄,利用委托屬性可以封裝SharedPreference
將數(shù)據(jù)存儲操作委托給代理類有幾個好處
1.則精簡了代碼芋齿,方便了存儲與讀取調(diào)用
2.與SP進行了解耦,后續(xù)如果要替換存儲庫竖瘾,只需要修改代理類即可
調(diào)用如下:
object Pref: PreferenceHolder() {
var isFirstInstall: Boolean by bindToPreferenceField(false)
var time: Long? by bindToPreferenceFieldNullable()
}
具體實現(xiàn)可見:SharedPreferences用Kotlin應(yīng)該這樣寫
帶狀態(tài)的LiveData
目前我們在開發(fā)的過程中越來越多的使用MVVM模式與ViewModel
我們也常常用LiveData來標(biāo)識網(wǎng)絡(luò)請求狀態(tài)
我們需要定義請求開始沟突,請求成功花颗,請求失敗捕传,三個LiveData
這其實也是很冗余重復(fù)的代碼,因此我們可以進行一定的封裝扩劝,封裝一個帶狀態(tài)的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
}
}
通過以上封裝庸论,可以比較優(yōu)雅簡潔的封裝網(wǎng)絡(luò)請求的loading,success,error狀態(tài),精簡了代碼棒呛,結(jié)構(gòu)也比較清晰
DSL
DSL(domain specific language)聂示,即領(lǐng)域?qū)S谜Z言:專門解決某一特定問題的計算機語言,比如大家耳熟能詳?shù)?SQL 和正則表達式簇秒。
但是鱼喉,如果為解決某一特定領(lǐng)域問題就創(chuàng)建一套獨立的語言,開發(fā)成本和學(xué)習(xí)成本都很高趋观,因此便有了內(nèi)部 DSL 的概念扛禽。所謂內(nèi)部 DSL,便是使用通用編程語言來構(gòu)建 DSL皱坛。比如编曼,本文提到的 Kotlin DSL,我們?yōu)?Kotlin DSL 做一個簡單的定義:
“使用 Kotlin 語言開發(fā)的剩辟,解決特定領(lǐng)域問題掐场,具備獨特代碼結(jié)構(gòu)的 API 》妨裕”
舉個例子熊户,我們使用TabLayout時,如果要為他添加監(jiān)聽吭服,需要實現(xiàn)以下3個方法
override fun onTabReselected(tab: TabLayout.Tab?){
}
override fun onTabUnselected(tab: TabLayout.Tab?){
}
override fun onTabSelected(tab: TabLayout.Tab?){
}
其實我們一般只會用到onTabSelected方法敏弃,其余兩個一般是空實現(xiàn)
我們利用DSL對OnTabSelectedListener進行封裝,即可避免寫不必要的空實現(xiàn)代碼
具體實現(xiàn)如下:
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.先定義一個類去實現(xiàn)回調(diào)接口噪馏,并且實現(xiàn)它的回調(diào)方法麦到。
- 2.觀察回調(diào)方法的參數(shù)抱究,提取成一個函數(shù)類型(function type),并且按照需要使用類型別名給函數(shù)類型起一個別稱虚汛,并且用私有修飾痛垛。
- 3.在類里面聲明一些可空的函數(shù)類型的可變(var)私有成員變量,并且在回調(diào)函數(shù)中拿到對應(yīng)的變量實現(xiàn)它的invoke函數(shù)粹淋,傳入對應(yīng)的參數(shù)吸祟。
- 4.在類中定義一些跟回調(diào)接口一樣名字,但是參數(shù)是對應(yīng)的函數(shù)類型的函數(shù)桃移,并且將函數(shù)類型賦值給當(dāng)前類的對應(yīng)的成員變量屋匕。
- 5.定義一個成員函數(shù),參數(shù)是一個帶有我們定好那個類的接受者對象并且返回Unit的Lambda表達式借杰,在函數(shù)里創(chuàng)建相應(yīng)的對象过吻,并且使用also函數(shù)把Lambda表達式傳進去。
調(diào)用如下:
tabLayout.addOnTabSelectedListener(registerOnTabSelectedListener {
onTabSelected { vpOrder.currentItem = it?.position ?: 0 }
})
如上蔗衡,就可以避免寫一些不必要的空實現(xiàn)代碼了
本文在開源項目:https://github.com/Android-Alvin/Android-LearningNotes 中已收錄纤虽,里面包含不同方向的自學(xué)編程路線、面試題集合/面經(jīng)绞惦、及系列技術(shù)文章等逼纸,資源持續(xù)更新中...
作者:RicardoMJiang
鏈接:https://juejin.cn/post/6921337734216810504