最近都是在用Kotlin开发Android项目,总结了一些心得在这里和大家分享
kotlin定义变量有三种形式
1)使用var定义可修改变量,最常见的用法,也是很灵活,
private var point: Point? = null
//使用的时候,因为point是可空的,所以有两种用法
println(point?.x) //如果你不能确保point是否为空
println(point!!.x) //如果你能确保point一定不为空,否则point为空,这里会报运行时空指针
//下面用法会报编译时错误,因为point是Point?类型,所以point有可能是null
println(point.x)
从上面的注释,我们可以发现,kotlin从编译时预防了空指针的可能性,VeryGood的特性,能够避免很多人为导致的空指针错误
2)使用val定义不可修改的变量
private val point = Point(20, 20)
这种情况下point一定不会为空,因为val定义的变量必须要初始化,从这个角度来看,又避免了人为导致的空指针错误
比如下面会产生编译时错误
point = Point(30, 30)
需要注意的是,point不能被修改,但是Point类里面的成员变量是可修改的,所以下面操作是允许的
point.x = 30
point.y = 30
3)使用lateinit var,可以使得变量的初始化可以延迟到需要的时候
比如在使用dagger的时候,需要inject,如下
@Inject lateinit var mPresenter: MapHomePresent
但是需要慎用lateinit, 因为你可能后面忘记了初始化,但是编译器又不会报错提醒。只有运行之后才会检测到,如下
fun main(args: Array<String>) {
var test = Test()
println(test.a)
}
class Test {
lateinit var a: String
}
会报错如下:
Exception in thread "main" kotlin.UninitializedPropertyAccessException: lateinit property a has not been initialized
at Test.getA(Simplest version.kt:14)
at Simplest_versionKt.main(Simplest version.kt:10)
注意下面是两种类型
var a: String? = null
var b: String = null
b可以自动转换成a, a需要使用a!!转换成b,这种在定义方法的时候特别有用,如下举例
fun test(a: String) {
a.apply{
print(this)
}
}
上面这个方法在传入参数时,必须保证这个参数不为空,比如下面会报编译错误
var temp: String? = null
test(temp)//这里会报编译错误,L类型自动转换
test(temp!!)//ok,但是需要在别的地方把temp重新赋值为非null
反过来,下面是可以调用的
fun test(a: String?) {
a?.apply{
print(this)
}
}
var temp: String = null
test(temp)//会自动转换
可以看下data class的定义,它是专门用来存储数据的,很适合entity的场景,比如从服务器拉取数据。
比如使用apply函数,可以使得代码看起来非常简洁
mOption.apply {
locationMode = AMapLocationClientOption.AMapLocationMode.Hight_Accuracy//可选,设置定位模式,可选的模式有高精度、仅设备、仅网络。默认为高精度模式
isNeedAddress = false
/**
* 设置是否优先返回GPS定位结果,如果30秒内GPS没有返回定位结果则进行网络定位
* 注意:只有在高精度模式下的单次定位有效,其他方式无效
*/
isGpsFirst = false // GPS优先会导致反应速度很慢
// 设置是否开启缓存
isLocationCacheEnable = true
// 设置是否单次定位
isOnceLocation = true
//设置是否等待设备wifi刷新,如果设置为true,会自动变为单次定位,持续定位时不要使用
isOnceLocationLatest = true
//设置是否使用传感器
isSensorEnable = true
interval = 1000
// 设置网络请求超时时间
httpTimeOut = 60000
//设置是否开启wifi扫描,如果设置为false时同时会停止主动刷新,停止以后完全依赖于系统刷新,定位位置可能存在误差
isWifiScan = true
}
对比下,下面的普通用法
mOption.locationMode = AMapLocationClientOption.AMapLocationMode.Hight_Accuracy//可选,设置定位模式,可选的模式有高精度、仅设备、仅网络。默认为高精度模式
mOption.isGpsFirst = false//可选,设置是否gps优先,只在高精度模式下有效。默认关闭
mOption.httpTimeOut = 30000//可选,设置网络请求超时时间。默认为30秒。在仅设备模式下无效
mOption.interval = 2000//可选,设置定位间隔。默认为2秒
mOption.isNeedAddress = true//可选,设置是否返回逆地理地址信息。默认是true
mOption.isOnceLocation = false//可选,设置是否单次定位。默认是false
mOption.isOnceLocationLatest = false//可选,设置是否等待wifi刷新,默认为false.如果设置为true,会自动变为单次定位,持续定位时不要使用
AMapLocationClientOption.setLocationProtocol(AMapLocationClientOption.AMapLocationProtocol.HTTP)//可选, 设置网络请求的协议。可选HTTP或者HTTPS。默认为HTTP
mOption.isSensorEnable = false//可选,设置是否使用传感器。默认是false
mOption.isWifiScan = true //可选,设置是否开启wifi扫描。默认为true,如果设置为false会同时停止主动刷新,停止以后完全依赖于系统刷新,定位位置可能存在误差
mOption.isLocationCacheEnable = true //可选,设置是否使用缓存定位,默认为true
一对比发现,上面这种写法真的是很简洁
?: 的意思是,左边的表达式没有成功,则使用右边的结果;如下,person是null,所以person?.name
不会执行,所以最终a == "null"
var person: Person? = null
var a = person?.name ?: "null"
配合闭包也可以使用,如下代码:
screenMarker?.apply {
val point = aMap!!.projection.toScreenLocation(position)
point.y -= SizeUtils.sp2px(125f)
val target = aMap!!.projection.fromScreenLocation(point)
val animation = TranslateAnimation(target)
animation.setInterpolator { input ->
// 模拟重加速度的interpolator
if (input <= 0.5) {
(0.5f - 2.0 * (0.5 - input) * (0.5 - input)).toFloat()
} else {
(0.5f - Math.sqrt(((input - 0.5f) * (1.5f - input)).toDouble())).toFloat()
}
}
//整个移动所需要的时间
animation.setDuration(600)
//设置动画
setAnimation(animation)
//开始动画
startAnimation()
}?:KLog.d(TagObject.TAG, "screenMarker is null")
是不是看上去,代码连贯性很强。
当你的类包含太多的东西,你想把它们隔离到另外一个类,又不想使用类引用的方式,你就可以使用companion object,如下
class AndroidFragment : MainFragment() {
override fun getAdapter(list: ArrayList): BaseBindingAdapter<*> {
return FuckGoodsAdapter(list)
}
override fun getType(): String {
return ANDROID
}
//companion object的好处是,外部类可以直接访问对象,不需要通过对象指针
companion object {
val ANDROID = "ANDROID"
fun newInstance(): Fragment {
val fragment = AndroidFragment()
val bundle = Bundle()
fragment.arguments = bundle
return fragment
}
}
}
可以结合一起使用,又能实现代码分离,增强代码的可读性
有些时候你不想使用定义新的接口去实现回调,那就可以考虑使用闭包。如下代码:
protected fun submit(observable: Observable>, block: (T) -> Unit) {
addDisposable(
observable.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
if(it != null && !it.error && it.results != null) {
block(it.results)
}else if (it == null){
KLog.d(TagObject.TAG_Release, "result is null")
}else {
KLog.d(TagObject.TAG_Release, "res.error:" + it.error + ",res.results:" + it.results)
}
},
{
KLog.d(TagObject.TAG_Release, "error android Presenter" + it.message)
}
)
)
}
调用如下:
submit(mModel.getData(page, type)){
view.setData(it)
}
这样写代码,会显示非常简洁,也避免了创建大量的接口回调类。
首先选中Java类,然后按ctrl+shift+A,弹出一个“enter action or option name”的对话框,然后输入”convert Java file to kotlin file”,就像下面的截图:
点击“Convert java File to Kotlin File”(也可以使用快捷键),IDE就自动帮我们把Java文件转换成kotlin, 一般情况下,转换出来的文件,但是不排除需要做一些修改。但是相对来说也省事多了