前言
在Activity/Fragment
获取传递过来的参数的时候,需要使用intent/arguments
来获取对应的值,每次都要写一堆的intent.getStringExtra
等等的代码,为什么不使用kotlin的委托属性来减少重复代码呢?
默认获取参数方式
下面有几种常用的获取参数的方法
使用默认值
1 | // 声明 |
注意不能直接在声明属性的时候获取值,如
private val test: String = intent.getStringExtra("test")
这样写会直接报错的,因为在init
的时候getIntent
返回的是空。
说下缺点,很明显,每次都要写get
的代码,很烦,而且每次使用还要加?
。
使用lateinit
1 | // 由于lateinit声明,所以类型不能为空 |
说下缺点,同第一种,也是每次都要写,但是比第一种要少加个?
。
使用lazy
(属于kotlin的标准委托)
1 | private val test by lazy { |
比上面两种好多了,但是lazy
一般会带有额外的开销,除非使用LazyThreadSafetyMode.NONE
模式,而且lazy
是用val
声明,如果要修改,那就得重新声明一个属性…
改变方式
那么有没有好的方法来减少重复代码呢,并且即可声明为val
,也可声明为var
呢?我们要达到的效果应该是下面这样1
2
3private val test by extraAct("test", "")
private var test1 by extraAct("test1", "")
这里就要用到kotlin的委托属性了,有一种属性,在使用的时候每次都要手动实现它,但是可以做到只实现一次,并且放到库中,一直使用,这种属性称为委托属性。具体介绍可见https://www.kotlincn.net/docs/reference/delegated-properties.html。
委托属性的级别语法是
val/var <属性名>: <类型> by <表达式>
实现接口,重写方法
ReadWriteProperty
实际上我们要写的就是实现ReadWriteProperty
接口,然后在getValue
和setValue
,进行获取和设置对应的值。1
2
3
4
5
6
7
interface ReadWriteProperty<in R, T> {
operator fun getValue(thisRef: R, property: KProperty<*>): T
operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}
- 首先声明一个类,然后实现
ReadWriteProperty
接口 - 创建两个参数,一个是参数名
extraName
,类型为String
;一个是默认值default
,泛型 - 然后声明一个属性,用于保存获取对应字段的值,可空类型。
1 | class ActivityExtras<T>(private val extraName: String, private val defaultValue: T) : |
因为我们是在Activity
里面获取对应的Intent
参数的值,所以thisRef
的类型就必须写成Activity
。
getValue
然后来获取对应的字段的值:1
2
3
4override fun getValue(thisRef: Activity, property: KProperty<*>): T {
return extra ?: thisRef.intent?.get<T>(extraName)?.also { extra = it }
?: defaultValue.also { extra = it }
}
来说明下这段代码:
- 首先是如果
extra
不为空,则返回extra
- 如果
extra
是空的,则判断intent
的参数的值,如果值不为空,则返回,并且将值赋予extra
- 如果
intent
参数的值也为空,则返回defaultValue
,并且将值赋予extra
Intent
的get
扩展见 https://github.com/Ifxcyr/ActivityMessenger
setValue
设置值的话就很简单了,直接将value
赋值给extra
就行了1
2
3override fun setValue(thisRef: Fragment, property: KProperty<*>, value: T) {
extra = value
}
结束
粗劣调用
调用的时候呢,就这样写1
private val test by ActivityExtras<String?>("test", null)
改进
emmmm,还是有一点区别,如果我不想用默认值的时候,直接替我声明为null
。那么就要定义函数,返回的类型是ActivityExtras
就可以。1
2
3
4fun <T> extraAct(extraName: String): ActivityExtras<T?> = ActivityExtras(extraName, null)
fun <T> extraAct(extraName: String, defaultValue: T): ActivityExtras<T> =
ActivityExtras(extraName, defaultValue)
最终调用效果1
2
3
4
5
6
7
8private val test: String? by extraAct("test") // 类型为String?
private val test1 by extraAct<String>("test1") // 类型为String?
private var test2 by extraAct("test2", "1") // 类型为String
// 修改的时候,直接赋值就行
test2 = "123"
结论
委托属性其实很简单,实现了ReadWriteProperty
接口之后,在调用属性的get
和set
的时候自动帮你调用对应的getValue
和setValue
函数,省去了重复的代码操作。
如果是ReadOnlyProperty
的话,那么就只提供getValue
函数,只能获取,不能修改属性。
对应Fragment代码
对应的Fragment代码,使用的时候,同ActivityExtras
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29/**
* 获取Intent参数,Fragment
* 示例同[ActivityExtras]
*/
class FragmentExtras<T>(private val extraName: String, private val defaultValue: T) :
ReadWriteProperty<Fragment, T> {
/**
* getExtras字段对应的值
*/
private var extra: T? = null
override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
// 如果extra不为空则返回extra
// 如果extra是空的,则判断arguments的参数的值,如果值不为空,则将值赋予extra,并且返回
// 如果arguments参数的值也为空,则返回defaultValue,并且将值赋予extra
return extra ?: thisRef.arguments?.get<T>(extraName)?.also { extra = it }
?: defaultValue.also { extra = it }
}
override fun setValue(thisRef: Fragment, property: KProperty<*>, value: T) {
extra = value
}
}
fun <T> extraFrag(extraName: String): FragmentExtras<T?> = FragmentExtras(extraName, null)
fun <T> extraFrag(extraName: String, defaultValue: T): FragmentExtras<T> =
FragmentExtras(extraName, defaultValue)