委托属性是kotlin一个非常好用的特性,它可以让我们通过类来封装控制属性的set和get方法,通过这种方式来复用代码,能让代码更加简洁,下面看看它在Android开发中的应用。
案例:如果要实现在给String类型的属性赋值时,去掉字符串两边的空格,最常见的实现方式是这样的。
class Example {
var param: String = ""
set(value) {
field = value.trim()
}
...
}
这样操作没什么问题,但是如果有很多个String属性,都需要这个功能,那么代码就会比较重复,而且看上去会很乱。如果是使用委托属性,我们需要先定义一个类,实现ReadWriteProperty<T, V>接口,用来处理String属性的set和get方法,T表示限定声明属性所属类的类型,V表示限定属性自身的类型,这里不限定是某种类的String属性,所以实现接口ReadWriteProperty<Any?, String>,如果限定XX类的String属性,改为实现ReadWriteProperty<XX, String>即可。
1class TrimDelegate : ReadWriteProperty<Any?, String> {
2
3 private var trimmedValue: String = ""
4
5 override fun getValue(
6 thisRef: Any?,
7 property: KProperty<*>
8 ): String {
9 return trimmedValue
10 }
11
12 override fun setValue(
13 thisRef: Any?,
14 property: KProperty<*>, value: String
15 ) {
16 trimmedValue = value.trim()
17 }
18}
在需要实现该功能的属性上使用by TrimDelegate()
即可,如下
class Example {
//使用委托属性此处简洁很多
var param1 by TrimDelegate()
var param2 by TrimDelegate()
}
如果有很多属性都需要该功能,使用委托属性可以非常明显的简化代码,下面是一个完整示例,可在线运行查看效果。
在Android开发中,在Fragment中一般使用arguments来进行传参,在创建Frament时,把参数设置在arguments中。
val demoFragment = DemoFragment().apply {
arguments = Bundle().apply {
putString("name", "Demo from arguments")
}
}
然后在Fragment的onCreate中通过arguments来读取参数,然后保存至成员属性中。
private var name: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
name = arguments?.getString("name")
}
下面看看如何使用委托属性来简化,为了统一处理Bundle各种类型的参数,先给Bundle添加一个扩展方法。
1fun <T> Bundle.put(key: String, value: T) {
2 when (value) {
3 is Boolean -> putBoolean(key, value)
4 is String -> putString(key, value)
5 is Int -> putInt(key, value)
6 is Short -> putShort(key, value)
7 is Long -> putLong(key, value)
8 is Byte -> putByte(key, value)
9 is ByteArray -> putByteArray(key, value)
10 is Char -> putChar(key, value)
11 is CharArray -> putCharArray(key, value)
12 is CharSequence -> putCharSequence(key, value)
13 is Float -> putFloat(key, value)
14 is Bundle -> putBundle(key, value)
15 is Parcelable -> putParcelable(key, value)
16 is Serializable -> putSerializable(key, value)
17 else -> throw IllegalStateException("Type of property $key is not supported")
18 }
19}
接下来是实现委托类,因为是针对Fragment类的所有类型属性,所以实现接口ReadWriteProperty<Fragment, T?>,在getValue方法中,我们根据属性的名字从Fragment的arguments中读取数据,在setValue时把数据保存到Fragment的arguments中。
1class FragmentArgumentDelegate<T : Any?> :
2 ReadWriteProperty<Fragment, T?> {
3
4 @Suppress("UNCHECKED_CAST")
5 override fun getValue(
6 thisRef: Fragment,
7 property: KProperty<*>
8 ): T? {
9 val key = property.name
10 return thisRef.arguments?.get(key) as? T
11 }
12
13 override fun setValue(
14 thisRef: Fragment,
15 property: KProperty<*>, value: T?
16 ) {
17 val args = thisRef.arguments
18 ?: Bundle().also(thisRef::setArguments)
19 val key = property.name
20 value?.let { args.put(key, it) } ?: args.remove(key)
21 }
22}
在声明属性和给属性赋值时这样使用,就会从arguments中获取和保存至arguments中。
//声明属性
private var userId: Int? by FragmentArgumentDelegate()
private var name: String? by FragmentArgumentDelegate()
//给属性赋值
DemoFragment().apply {
this.userId = userId
this.name = name
}
为了更加简化声明属性的代码,我们可以定义一个方法argument()
来替换FragmentArgumentDelegate。
fun <T : Any> argument() = FragmentArgumentDelegate<T>()
//声明属性的时候就可以这样
private var userId: Int? by argument()
private var name: String? by argument()
同样的在使用SharedPreferences时,我们也会从SharedPreferences中获取值,在更新值的时候同步到SharedPreferences中,代码会是这样的。
private val preferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
var uuid: String?
get() {
return preferences.getString("uuid", null)
}
set(value) {
preferences.edit().putString("uuid", value).apply()
}
如果属性很多,这样的代码就会很重复,我们一样可以通过委托属性来优化这段代码,之前我们都是定义一个类来代理属性,这里之前有些不一样,这个地方会依赖具体的preferences
,我们有两种方式可以处理,一种是将preferences作为参数传入到委托类中,第二种就是使用扩展方法。
1fun SharedPreferences.string(
2 defaultValue: String? = null,
3 key: (KProperty<*>) -> String = KProperty<*>::name
4): ReadWriteProperty<Any, String?> =
5 object : ReadWriteProperty<Any, String?> {
6 override fun getValue(
7 thisRef: Any,
8 property: KProperty<*>
9 ) = getString(key(property), defaultValue)
10
11 override fun setValue(
12 thisRef: Any,
13 property: KProperty<*>,
14 value: String?
15 ) = edit().putString(key(property), value).apply()
16 }
在声明属性的地方改成这样即可。
private val preferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
var uuid by preferences.string()
这样是不是比之前更加简洁了?下面是上面示例的完整代码。
文件 | 大小 | 修改时间 |
---|---|---|
app | 2022年03月18日 | |
build.gradle | 358 B | 2022年03月18日 |
gradle/wrapper | 2022年03月18日 | |
gradle.properties | 1 kB | 2022年03月18日 |
gradlew | 6 kB | 2022年03月18日 |
gradlew.bat | 3 kB | 2022年03月18日 |
local.properties | 437 B | 2022年03月18日 |
settings.gradle | 333 B | 2022年03月18日 |