android stutdio支持一键转化的方式,通过快捷键ctrl +alt + shift + k将整个java文件转化为kt文件,不过可能需要自己解决一些代码不一致的冲突,而且有些时候转化后的代码和我们直接写的代码还是有差别的。
android studio3.0默认支持kotlin,虽然是默认支持kotlin,但也是需要增加一些配置,不过这个as已经给我们做好了智能提示和自动添加的功能。在我们第一次创建出kt文件的时候,将会有一个kotlin的配置提示,我们只需要点击configure,然后选择全局配置,那么as将会在gradle中添加上kotlin的依赖
[图片上传失败…(image-4852fe-1542511738777)]
gradle文件如下所示:
[图片上传失败…(image-17de9f-1542511738777)]
这样我们就使用kotlin的代码库了。
之前项目中用的是view的绑定库是ButterKnife,就个人而言,还是觉得挺好用的,毕竟绑定View的代码都能自动生成,不过如果在kt文件中,不能直接引入这个库,需要重新引入kotlin中的ButterKnife库https://github.com/JakeWharton/kotterknife,不过我们可以不用这个库了,因为kotlin给我们提供了一套更为简便的库,我们只需要在gradle中引入kotlin-extension,并且在kt文件中import相对应的xml,就能直接通过id的方式引用view。
//在kt文件中
import kotlinx.android.synthetic.main.activity_member_center.*
//在gradle文件中
apply plugin: 'kotlin-android-extensions'
比如说我们在xml中有这样一个View:
这样我们在kt文件中,可以像下面的方式这样调用:
private fun initRv() {
adapter = MemberInfoAdapter(this, null)
memberRv.layoutManager = LinearLayoutManager(this)
memberRv.itemAnimator?.changeDuration = 0
memberRv.adapter = adapter
}
这个memberRv就是RecyclerView在xml中指定的id。
在kotlin中,声明所有变量都需要3个关键字修饰,var、val和const。在kotlin中是没有final修饰符的,final修饰符在kotlin中其实就相当于val
class a {
var c = 1;
val b: String
fun getB(): String {
return c > 3 ? "haha" : "xixi"
}
fun add() {
c++
}
}
在java中,静态属性和静态方法只需要一个static属性就能搞定了,但是在kotlin,它有一个单独的块来标识静态块的初始化,companion object:
companion object {
val intentFrom: String = "intent_extra"
fun startMemberCenterActivity(context: Context, launchMemberCenter: LaunchMemberCenter) {
var intent = Intent(context, TestAc::class.java)
intent.putExtra(intentFrom, launchMemberCenter)
if (context is Activity) {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
context.startActivity(intent)
}
}
这个代码块有几个需要注意的点,
//在java文件中调用
public void start() {
TestAc.Companion.startMemberCenterActivity(context, this);
}
//在kt文件中调用
public void start() {
TestAc.startMemberCenterActivity(context, this);
}
在变量定义时,我们可以通过kotlin特有的方式来标识变量是否可空:
private var adapter: MemberInfoAdapter? = null
只需要的变量类型后面添加?,就表示这个变量是可以为null的,如果不添加,默认这个变量不能为null。一旦我们添加了?,在后续的代码中,如果有引用到这个变量的地方,如果没有做判空处理将会编译不过,这个判空处理还是挺方便的,
override fun refreshDiscoveryList(mixFindInfoList: MutableList?) {
adapter?.notifyMixFindInfoChange(mixFindInfoList)
}
Equality check should be used instead of elvis for nullable boolean check
在调用时,如果adaper为空了,将不会执行后续的操作。
今天遇到了一个符号:
var xie = a ?: ""
这个?:是kotlin的elvis用法,属于两目运算符,就是简单的if、else缩写,
当a不为null,取a的值,当a为null,取""
kotlin接口和java接口的使用区别
class TestAc : BaseMvpActivity(), MemberCenterPresent.MemberInfoChange,
KKAccountManager.KKAccountChangeListener, View.OnClickListener {
其中BaseMvpActivity继承,而其他三个都是接口继承。
在kotlin中,用when表达式将将java中的switch替换掉了。
最简单的形式如下:
override fun onClick(v: View?) {
when (v?.id) {
R.id.icBack -> finish()
R.id.btnOpenLayout -> btnOpenLayoutClick()
else -> {
}
}
而且,when里面的条件判断可以加入比较复杂的判断,如下:
when (x) {
in 1..10 -> print("x is in the range")
in validNumbers -> print("x is valid")
!in 10..20 -> print("x is outside the range")
else -> print("none of the above")
}
kotlin中当含有非空的构造函数得继承,有以下这几种方式:
class MemberCenterAdapter(context: Context?, mixFindInfoList: ArrayList?) : TopicTabListAdapter(context, mixFindInfoList)
相当于子类的构造器声明就放在了class的定义上,然后会对应父类相应的构造器。还有另外一种实现方式,主要用于多个构造器的声明。
constructor(context: Context?, mixFindInfoList: ArrayList?): super(context, mixFindInfoList)
在kotlin中,推荐我们使用下标的方式来访问元素,就像在访问数组一样。
private var itemTypeList: ArrayList = ArrayList()
itemTypeList[2] = ITEM_TYPE_ACTIVITY
对于kotlin中的列表遍历,增加了许多遍历方式。最简单的如下所示:
//kotlin
for (i in startIndex..mixFindInfoList!!.size )
//java
for(int i = startIndex; i < mixFindInfoList.size(); i++)
在kotlin中,推荐我们使用until的方式进行遍历,这样,我们处理起来也就像是在处理流一样。
// a until b表示从a到b进行遍历,并且将遍历的值传递给下游,
// map将上游传递过来的值进行转化,并传递给下游
// forEach对转化后的值进行处理
(startIndex until mixFindInfoList!!.size)
.map { mixFindInfoList[it] }
.forEach {
itemTypeList.add(it.item_type)
}
之前在java中用惯了三目运算符,看着kotlin的三目运算符挺不习惯的。
//java, 如果a为true,则取b,否则取c
a ? b : c
//kotlin, 利用if else组成三目运算符,但是不需要括号
if(a) b else c
在kotlin中,如果多个分支语句都返回值,那么我们可以将返回值放在分支语句的外侧。
//最原始的版本
when (position) {
0, 2 -> return null
1 -> return Utility.getSafely(mixFindInfoList, 0)
else -> {
return Utility.getSafely(mixFindInfoList, position - 2)
}
}
优化后的版本:
return when (position) {
0, 2 -> null
1 -> Utility.getSafely(mixFindInfoList, 0)
else -> {
Utility.getSafely(mixFindInfoList, position - 2) }
}
这种时候,就能够返回每一个when分支对应的值
对于if else语句也能这么使用
return if (activityListExist()) {
if (vipBannerExist()) {
when (position) {
0, 2 -> null
1 -> Utility.getSafely(mixFindInfoList, 0)
else -> {
Utility.getSafely(mixFindInfoList, position - 2)
}
}
} else {
if (position == 0 || position == 1) {
null
} else {
Utility.getSafely(mixFindInfoList, position - 2)
}
}
} else {
if (position == 0) {
null
} else {
Utility.getSafely(mixFindInfoList, position - 1)
}
}
}
这种时候,每一种判断的分支对应一个值,返回命中的分支对应的值
java中类型判断和转化:
if (holder instance of MemberVipHeaderHolder) {
((MemberVipHeaderHolder)holder).bindData(title, TYPE_VIP_HEADER, isVip, isBtnShow);
}
java中利用instance of进行类型判断,然后直接通过(type)instance进行类型强转,将instance转化为type类型的实例。如果instance不是type类型,那么将会抛出类型转化异常。
而在kotlin中,可以通过as和is关键字进行类型判断和转化
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (getItemViewType(position)) {
ITEM_TYPE_ACTIVITY -> {
if (holder is MemberAssignViewHolder) {
holder.bindData(activityList)
}
}
ITEM_TYPE_MEMBER_INFO_CARD -> {
if (holder is MemberInfoCardViewHolder) {
holder.bindData()
}
}
else -> {
holder.itemView.setPadding(0, 0, 0, if (position == itemCount - 1) UIUtil.dp2px(40f) else 0)
if (holder is BaseViewHolder) {
holder.holderType = BaseViewHolder.TYPE_MEMBER
}
super.onBindViewHolder(holder, position)
}
}
}
利用 a is type 来判断a是否是type类型,如果是type类型的话,那么kotlin将会自动为我们将a转化为type类型,而不需要我们再手动的利用as进行类型转化。如果kotlin直接利用as进行类型转化的话,如果类型不一致或者a为null时,也会抛出异常,但是kotlin提供了类型不一致时的安全转化,如下
val x: String ?= y as? String
上面的例子中,as?是一个安全转化符,如果失败,将会返回null,而不会直接进行类型转化。
在java中,需要升级到java8才支持lambda表达,而kotlin天生支持lambda表达式,lambda表达式的完成语法如下:
val sum = { x: Int, y: Int -> x + y }
上面的例子定义了一个函数,求得sum = x + y,lambda表达式总是括在大括号中,完整的参数声明和表达式都放在大括号内,函数体在->之后,->之前都是参数,并且可能有类型标注,如果推断出该lambda的返回值类型不是Unit,也就是返回值不为空,那么该lambda主体的最后一个表达式将被视为返回值
下面是某一个lambda的例子:
FindExchangeManager.getInstance().loadExchangeData(mContext, findInfo.discoveryModuleId,
holder.adapterPosition, findInfo, FindExchangeManager.ExchangeCallback { data, position, discoveryModuleId ->
if (Utility.isEmpty(mContext)) {
return@ExchangeCallback
}
val info = getMixInfoFromPosition(position)
if (data != null && info != null) {
info.topics = data
notifyItemChanged(position)
}
})
如果在lambda表达式中调用return,将会直接return掉整个上层函数,因为lambda表达式只是一个代码块,虽然看起来功能像是函数,不过也不能说在lambda表达式中不能够return,只要给return指定一个标签,就会结束掉标签所指定的代码块,就像上面的例子,这个lambda表达式是在ExchangeCallback中使用的,如果要结束这个lambda,那么只需要在lambda中return代码添加上@ExchangeCallback标签就行,就绘结束掉ExchangeCallback的这个方法。
kotlin extension的使用需要先导入相应的xml
//在上一篇的activity中
import kotlinx.android.synthetic.main.activity_member_center.*
//在这个viewHolder中
import kotlinx.android.synthetic.main.listitem_assign.view.*
对于这个导包操作,需要注意的是,kotlinx.android.synthetic.main是固定前缀,后面的是当前页面的layout文件名称,如果是activity,只需要再在后面添加上.*,那就能够在activity中直接以id的形式调用对应的View。如果是在viewHolder中,那么还需要添加上.view,在添加.*才能够使用,而且在viewHolder中,需要以其itemView为持有者来调用。
init {
context = itemView.context
itemView.btn_get_gift.setOnClickListener(this)
itemView.btn_not_gift.setOnClickListener(this)
}
如上所示,利用itemView来持有其中的view的id,并直接调用。
这个init块,通常都是来做类的初始化的,在构造器执行完之后,会调用这个init块,我们如果有什么需要做初始化的,只需要放在这个init块中
java中String转long类型的话,需要调用Long.paras(string)进行转化,如果string不是相应的的类型,那么将会抛出异常。而在kotlin中也有这个方法,不过还新增了几个转化的方法:
//如果string不是一个合法的类型,那么将会抛出异常NumberFormatException
string.toLong()
//如果string不是一个合法的类型,那么将会返回null
string.toLongOrNull()
kotlin中有专门的rx-kotlin库,刚开始并没有引入这个库,所以尝试着还是使用rxjava
Observable.timer(getDelayTime(), TimeUnit.MILLISECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
getGrabResult(activityId, thirdId)
})
还是一样的用法,不过好的一点是可以使用lambda表达式了,subscribe({})中间的花括号就是lambda表达式
在java中,使用todo标识并不会出现什么问题,在kotlin中,一旦我们实现了接口或者抽象类,那么我们自动生成的实现方法,将会带上kotlin的todo标注
override fun refreshServerTime(time: Long?) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
如果我们没有删除掉todo这行代码,那么kotlin将会直接报错,kotlin.NotImplementedError,因为这个todo是kotlin默认我们将会删除掉的,如果在运行时还存在,那么执行到todo,并不会跳过,而是会执行,然后报错。
在kotlin中,如果需要在kotlin的高阶函数表达式返回,而不需要直接在外层函数中返回,或者嵌套循环,想从内层循环终止外层循环等,这种时候我们就需要用到kotlin的label了。比如说下面的例子:
private fun canAutoContinue(pos: Int): Boolean {
if (commonGoodList == null || continueGoodList == null || commonGoodList!!.size < pos) {
return false
}
val commonGood: RechargeGood? = commonGoodList!![pos]
continueGoodList!!.forEach {
if (it.upRenewId == commonGood?.id) {
return true
}
}
print("1111")
return false
}
上面的在forEach中,return的执行将会直接将会结束掉canAutoContinue方法,如果我们只想结束掉forEach,我们可以像下面这样做。
private fun canAutoContinue(pos: Int): Boolean {
if (commonGoodList == null || continueGoodList == null || commonGoodList!!.size < pos) {
return false
}
val commonGood: RechargeGood? = commonGoodList!![pos]
continueGoodList!!.forEach lit@{
if (it.upRenewId == commonGood?.id) {
return@lit
}
}
print("1111")
return false
}
如上面的lit label,那么这样return将会结束lit所指定的lable循环,从而继续往下执行。
比如说for循环嵌套
outFor@ for(x in 1..5) {
for (y in 1..6) {
if(x == y) {
break@outFor
}
}
}
一旦满足了x==y,然么将会直接跳出外层的for循环,否则一般的break,只会中断内层的for循环。要为一个表达式加标签,我们只要在其前加标签即可。
在返回和跳转语句中,可以指定标签来表示结束哪个标签对应的代码段
如果需要在lambda表达式
kotlin定义属性时,可以同时定义get和set方法,而不需要再重新定义方法去赋值。
private var delayTime: Long
private set(value) {
delayTime = value
}
get() {
return KKConfigManager.getInstance().
getConfig(KKConfigManager.ConfigType.GET_RECHARGE_ORDER_DELAY)
.toLongOrNull() ?: 1000
}
而且,可以添加上访问限定符。而这个get和set方法的设置,主要是通过代码中的位置来标示修饰的哪个变量。
Kotlin 中的类不能有字段, 然而我们在有时在自定义访问器时,也就是get和set方法时,需要又一个幕后字段,这个字段是kotlin提供的,field,我们可以直接使用。
var counter = 0 // 注意:这个初始器直接为幕后字段赋值 set(value) {
if (value >= 0) field = value }
val nameHash:Int = 3
get() {
field = 5
return 10
}
有可能我们需要临时缓存变量的值,因为可能其变量的值可能和返回的值不一致,所以一旦我们需要知道变量的值,我们便可以通过幕后字段来访问。
幕后属性主要用于外部只能读,内部可以读写的需求下出现的。例如下面的例子:
val counter get() = _counter
private var _counter = 0
这个_counter就是counter的幕后属性,外部只能访问counter,不能访问_counter,而counter的值又是_counter来指定的。
在kotlin中,if else的三目运算符如下所示:
if(a) x else y
而我们也能通过扩展函数来扩展boolean
inline fun Boolean.yes(action: () -> T): Boolean {
if (this) action()
return this
}
inline fun Boolean.no(action: () -> T): Boolean {
if (!this) action()
return this
这样,我们在kt文件中,只要引入相应的类,定义在文件内,而非class下
import com.kuaikan.community.extend.yes
扩展函数是静态解析的,所以不能够进行重载,这就意味着我们,我们不能重载类原有的方法,比如说collection的add方法等…
对于扩展函数还有一个非常好用的东西,被扩展的类型可以为空,被称为可空接收者
fun Any?.toString(): String {
if (this == null) return "null"
// 空检测之后,“this”会自动转换为非空类型,所以下面的 toString() // 解析为 Any 类的成员函数
return toString()
}
既然kotlin支持扩展函数,当然也支持扩展属性了。
val List.lastIndex: Int
get() = size - 1
这样就给了List的一个属性lastIndex
关于out和in这两个关键字,out是用来输出的,也就是生产者,所以只能作为返回类型,相当于java中的extends,用来界定类型上限;in是用来输入的,所以只能作为消费类型,in类似于java中的super,用来界定类型下限
在kotlin中,声明单例变得非常简单,只需要一个关键字object用来修饰对象,就像变量声明一样,对象声明不是一个表达式,不能用在赋值语句右边,对象的初始化是线程安全的。如果需要引用该对象,我们只需要通过其名称来调用。
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ......
}
val allDataProviders: Collection get() = // ......
}
//调用
DataProviderManager.registerDataProvider(......)
类内部的伴生对象的声明如下:
class MyClass {
companion object { }
}
val x = MyClass.Companion
请注意,即使伴生对象的成员看起来像其他语言的静态成员,在运行时他们仍然是真实对象的实例成员,而且,例如还可以实现接口:
interface Factory { fun create(): T
}
class MyClass {
companion object : Factory {
override fun create(): MyClass = MyClass() }
}
如果我们想将伴生对象的成员生成为真正的静态方法和字段,我们我们可以使用@JvmStatic注解
对象表达式和对象声明之间有一个重要的语义差别:
这个是无关kotlin的,只是刚好在写kt代码时遇到的一个问题,
关于onAttachedToWindow和onDetachedFromWindow的触发时机,
顾名思义,onAttachedToWindow就是在这个View被添加到Window时触发的,通过了dispatchAttachedToWindow这个方法触发的,那么View被添加到window的时机又是什么,其实就是对应的activity在生命周期onResume的时候调用的,activity对应的view在onResume的时候被添加添加到window。且每一个view都只会被调用一次,父view调用在前,不论view的visibility状态都会被调用,适合做些view特定的初始化操作;
onDetachedFromWindow方法是在activity的生命周期destroy的时候被调用的,也就是act对应的view从window中移除的时候,且每个view只会被调用一次,父view的调用在后,也不论view的visibility状态都会被调用,适合做最后的清理操作;
java的class默认都是可以继承的,只有在声明为final的class才是不可继承的。而kotlin却是相反的,默认的类都是不可继承的,也就是默认的修饰符为final,只有显式的声明为open的类才是可以继承的。而对于抽象类,java和kotlin默认都是可以继承的,但是子类必须是抽象类或者实现了该类的所有抽象方法。
kotlin中的扩展函数是非常方便的,刚开始以为在java中不能够调用到kotlin中的扩展函数,后面发现不是的,对于我们定义的扩展函数类,在java中有相应的使用方式
//KotlinExt.kt
fun Int.dp2px(context: Context?): Int {
val scale = context?.resources?.displayMetrics?.density?.toInt()
return (this * (scale ?: 2) + 0.5f).toInt()
}
//java中调用
KotlinExtKt.dp2px(2, getContext());
我们只需要import相应的kotlinExt文件,然后就可以调用其中的方法,但使用方式和kotlin有不一致的地方,对于kt的扩展函数的接收者,也就是上面的Int,在java中会被认为是第一个入参, 如果扩展函数接受的是普通参数,那么这个参数就直接作为后续的入参。
如果扩展数据的第二个参数是lambda表达式,那么有java相应的生成方式:
//KotlinExt.kt
inline fun Boolean.yes(action: () -> T): Boolean {
if (this) action()
return this
}
//java中调用
KotlinExtKt.yes(true, new Function0
上面的Boolean的扩展yes方法,接收一个高阶函数,kt中用lambda表示,而在java中,需要以一个匿名内部类的方式替代,
在java中,如果定义一个非静态类内部类,默认将会持有外部类的引用,经常会造成外部类无法被回收,导致内存泄漏,而kotlin中,内部类默认不会持有外部类的引用,只有添加上inner关键字修饰,才会持有外部引用。
kotlin的密封类是java所没有的,在kotlin中,如果一个类被标明为密封类,那么其所有的子类都需要在父类中列出,作为密封类的嵌套内部类
kotlin中一些集合操作,比如说map和filter,对于我们来说是非常方便的,比如说筛选一个人群的年龄大于30的人的名称。
people.map(it.name).filter{it.age > 30}
但是上面这种方式,在链式调用的链足够长时,将会产生性能问题,因为链式调用的每一个步骤都会创建一个中间集合,用来存储中间结果,也就是说,每一个步骤的结果都存储在另外一个变量中。所以,kotlin给我们提供了另外一种使用方式,来避免创建中间对象。也就是惰性集合。sequencez作为惰性集合操作,可以优化map和filter等操作,将不会生成任何中间对象
people.asSequence().map(people.name).filter(it.startWith("a")).toList()
sequencez会将元数据生成一个流式对象,对于集合的每一个元素利用iterator遍历,执行map和filter,筛选出符合条件的数据。
惰性集合的操作包含了中间操作和末端操作,中间操作就是转换操作,末端操作就是筛选操作,在上面的例子里面,map是中间操作,filter是末端操作,如果没有末端操作,那么,中间操作也不会执行,既然这样,如果我们末端操作停止了,中间操作也会停止,也就是说,并不会保证所有的中间操作都能够执行。
在java中,butterknife库是一个众所周知的代替findViewId操作的库,如果我们直接在kotlin中想要使用butterknife这个库,那还是需要一些额外的操作的。
这个是butterknife作者提供的另外一个kotlin版本的替代库。使用方式如下:
val updateTime by bind(R.id.update_time)
虽然我没看KotterKnife内部实现,但是应该跟我们自己提供扩展函数的实现方式是一样的,我们可以通过自己提供扩展函数来设置做这些操作:
fun Activity.bind(@IdRes idRes: Int): Lazy {
return lazy { findViewById(idRes) }
}
fun View.bind(@IdRes idRes: Int): Lazy {
return lazy { findViewById(idRes) }
}
fun android.support.v7.widget.RecyclerView.ViewHolder.bind(@IdRes idRes: Int): Lazy {
return lazy { this.itemView.findViewById(idRes) }
}
fun Fragment.bind(@IdRes idRes: Int): Lazy {
return lazy { this.view?.findViewById(idRes) }
}
这个扩展数据其实就是代替我们执行findViewById操作,虽然我们是在初始化的时候定义了View的对象,但是它并不会马上执行,lazy关键字修饰的对象将会等到使用时再执行相应方法。也就是说,这个findViewById将会在setContentView之后执行,因为我们使用这个对象肯定是在其之后的。
其实在kotlin中,我们还是可以使用butterknife的,只是我们需要将butterknife的注解解释器替换为kotlin annotation Processor tool,这样,在kt文件中就能够使用butterknife的注解了,我们不需要担心会对之前的注解产生影响,因为kapt是兼容了annotationProcessor的。
annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'
====>
kapt 'com.jakewharton:butterknife-compiler:8.4.0'
替换了kapt之后,就可以进行第二步了,在kt中利用laterinit修饰对象,来保证view对象可以在通过butterknife库赋值
@BindView(R.id.user_v_layout)
lateinit var vLayout: ImageView
@BindView(R.id.comment_user_icon)
lateinit var userIconIV: ImageView
@BindView(R.id.comment_user_name)
lateinit var userNameTV: TextView