相信大家都知道,kotlin是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,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()的类型参数。
什么是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 ;
将数据存储操作委托给代理类有几个好处:
调用如下:
object Pref: PreferenceHolder() {
var isFirstInstall: Boolean by bindToPreferenceField(false)
var time: Long? by bindToPreferenceFieldNullable()
}
目前我们在开发的过程中越来越多的使用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 }
})
如上,就可以避免写一些不必要的空实现代码了。