前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >你应该知道的kotlin实用技巧

你应该知道的kotlin实用技巧

原创
作者头像
做个快乐的码农
发布2022-01-04 20:50:44
1.5K0
发布2022-01-04 20:50:44
举报
文章被收录于专栏:Android开发者

前言

众所周知,kotlin是google力推的用以取代java的android开发语言

kotlin使用起来比较方便,同时有许多语法糖

本文主要讲解了一些比较实用的kotlin技巧

自定义圆角矩形

在项目中,我们常常要定义圆角矩形背景,一般是用自定义drawable实现的

但是圆角矩形的背景与圆角常常会有细微的变化,而一旦变化我们又要新创建一个drawable文件

这样就会导致文件爆炸的问题

我们可以利用kotlin的扩展函数,来实现简单方便的圆角矩形背景

代码语言:txt
复制
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是这样写的

代码语言:txt
复制
startActivity(context, NewActivity::class.java)  

我们利用reified定义一个扩展函数

代码语言:txt
复制
// 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知道您想要的类型。

代码语言:txt
复制
User user = new Gson().fromJson(getJson(), User.class)

现在,让我们一起展示reified类型实化参数的魔法 我们将创建一个非常轻量级的扩展函数来包装Gson方法:

代码语言:txt
复制
inline fun <reified T> Gson.fromJson(json: String) = 
        fromJson(json, T::class.java)

现在,在我们的Kotlin代码中,我们可以反序列化JSON字符串,甚至根本不需要传递类型信息!

代码语言:txt
复制
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作为参数传递。

代码语言:txt
复制
// 注意需用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。

代码语言:txt
复制
<!-- 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进行了解耦,后续如果要替换存储库,只需要修改代理类即可

调用如下:

代码语言:txt
复制
object Pref: PreferenceHolder() {
    var isFirstInstall: Boolean by bindToPreferenceField(false)
    var time: Long? by bindToPreferenceFieldNullable()
}

具体实现可见:SharedPreferences用Kotlin应该这样写

带状态的LiveData

目前我们在开发的过程中越来越多的使用MVVM模式与ViewModel

我们也常常用LiveData来标识网络请求状态

我们需要定义请求开始,请求成功,请求失败,三个LiveData

这其实也是很冗余重复的代码,因此我们可以进行一定的封装,封装一个带状态的LiveData

定义如下:

代码语言:txt
复制
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)
        }
    }
}

使用如下

代码语言:txt
复制
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个方法

代码语言:txt
复制
override fun onTabReselected(tab: TabLayout.Tab?){

}

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

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

}

其实我们一般只会用到onTabSelected方法,其余两个一般是空实现

我们利用DSL对OnTabSelectedListener进行封装,即可避免写不必要的空实现代码

具体实现如下:

代码语言:txt
复制
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表达式传进去。

调用如下:

代码语言:txt
复制
tabLayout.addOnTabSelectedListener(registerOnTabSelectedListener {
    onTabSelected { vpOrder.currentItem = it?.position ?: 0 }
})

如上,就可以避免写一些不必要的空实现代码了

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 自定义圆角矩形
  • reified使用
    • startActivity例子
      • Gson解析例子
        • kotin接口支持SAM转换
          • 类委托
          • 属性委托
      • 委托
        • 带状态的LiveData
        • DSL
        相关产品与服务
        数据保险箱
        数据保险箱(Cloud Data Coffer Service,CDCS)为您提供更高安全系数的企业核心数据存储服务。您可以通过自定义过期天数的方法删除数据,避免误删带来的损害,还可以将数据跨地域存储,防止一些不可抗因素导致的数据丢失。数据保险箱支持通过控制台、API 等多样化方式快速简单接入,实现海量数据的存储管理。您可以使用数据保险箱对文件数据进行上传、下载,最终实现数据的安全存储和提取。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档