Android开发中kotlin编程语言的一些实用技巧

news/2024/11/28 7:47:09/

前言

相信大家都知道,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使用

reified,kotlin中的泛型实化关键字,使抽象的东西更加具体或真实。

我们举两个例子来看看怎么使用reified

startActivity例子

我们一般startActivity是这样写的

startActivity(context, NewActivity::class.java) 

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

// Functioninline fun <reified T : Activity> Activity.startActivity(context: Context) {startActivity(Intent(context, T::class.java))}// CallerstartActivity<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之前,只能使用objectrunAction(object : Action{override fun run() {println("run action")}}) 
// 1.4-M1支持SAM,OKrunAction {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? = nulloverride fun remove(element: T): Boolean {deletedItem = elementreturn 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()}

带状态的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?) -> Unitclass OnTabSelectedListenerBuilder : TabLayout.OnTabSelectedListener {private var onTabReselectedCallback: OnTabCallback? = nullprivate var onTabUnselectedCallback: OnTabCallback? = nullprivate var onTabSelectedCallback: OnTabCallback? = nulloverride fun onTabReselected(tab: TabLayout.Tab?) =onTabReselectedCallback?.invoke(tab) ?: Unitoverride fun onTabUnselected(tab: TabLayout.Tab?) =onTabUnselectedCallback?.invoke(tab) ?: Unitoverride fun onTabSelected(tab: TabLayout.Tab?) =onTabSelectedCallback?.invoke(tab) ?: Unitfun 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 }
})

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


http://www.ppmy.cn/news/41803.html

相关文章

在Ubuntu18.04或者20.04下搭建edk2运行环境

#更新完之后依次执行下面两条命令 1.apt-get update 2.apt-get upgrade 如果执行之后出现源不能更新的问题,到/etc/apt/sources.list.d 下删除对应的ppa源重新更新即可解决 git clone https://github.com/tianocore/edk2.git cd edk2 git submodule update --init 如果git cl…

C++之动态内存

12 动态内存 12.1动态内存与智能指针 动态分配对象的生存期与它们在哪里创建无关&#xff0c;只有当显示地被释放时&#xff0c;这些对象才会销毁。 new在动态内存中为对象分配空间并返回一个指向该对象的指针&#xff0c;可以对对象进行初始化。 delete接受以一个动态内存…

18从零开始学Java之switch分支语句中该怎么用?

作者&#xff1a;孙玉昌&#xff0c;昵称【一一哥】&#xff0c;另外【壹壹哥】也是我哦 CSDN博客专家、万粉博主、阿里云专家博主、掘金优质作者 前言 在上一篇文章中&#xff0c;壹哥给大家介绍了Java里的顺序、分支、循环结构的概念&#xff0c;并且重点给大家讲解了分支结…

Python 纯函数与副作用,可变参数与不可变参数

文章目录纯函数与副作用纯函数副作用参数传递不可变对象&#xff08;值类型&#xff09;可变对象&#xff08;引用类型&#xff09;参数传递方式纯函数与副作用 在Python的函数式编程中&#xff0c;Python既可以在调用时对实参进行修改&#xff0c;也可以通过返回值返回函数调…

咖啡卷到现在,他们开始往里面掺北京豆汁了

咖啡卷到现在&#xff0c;他们开始往里面掺北京豆汁了0. 导读1. 人手一杯&#xff0c;果咖大势所趋2. 双倍成瘾&#xff1a;茶咖和酒咖被重点推荐3. 地方小吃融入咖啡&#xff0c;比如北京豆汁4. 炙手可热的云南咖啡豆5. 咖啡、户外和环保&#xff1a;绑定可持续6. 小结0. 导读…

可选择的Elasticsearch好用的可视化客户端工具

前言 常言道&#xff1a;工欲善其事&#xff0c;必先利其器。对于我们开发和测试同学来说&#xff0c;在日常的工作中有一款趁手的工具那真实如虎添翼啊&#xff0c;工作效率可是蹭蹭蹭的往上长&#xff0c;节省下来的时间摸摸鱼该有多好啊。最近我们系统开始使用elasticsearc…

VAE 理论推导及代码实现

VAE 理论推导及代码实现 熵、交叉熵、KL 散度的概念 熵&#xff08;Entropy) 假设 p (x&#xff09;是一个分布函数&#xff0c;满足在 x 上的积分为 1&#xff0c;那么 p(x)p(x)p(x)的熵定义为 H(p(x))H (p (x))H(p(x))&#xff0c;这里我们简写为 H(p)H(p)H(p) H(p)∫p(x)…

45-Dockerfile-ARG/ENV指令

AGR/ENV指令前言ARG作用格式说明生效范围使用示例ENV作用格式说明使用环境变量使用示例ARG 和 ENV 的区别前言 本篇来学习下Dockerfile中的AGR/ENV指令 ARG 作用 定义一个可以在构建镜像时使用的变量 格式 ARG <name>[<default value>]说明 在执行 docker b…