编不下去了... 其实就是以前的一些项目采用了 Databinding,后面考虑用 Kotlin 从新写一遍,特此记录过程当中一些比较 tricky 的点。php
本文假设读者已经具有必定的 databinding 和 Kotlin 语法基础。java
databinding 的引入须要在 app 模块下的 build.gradle
中加入:android
android {
dataBinding.enabled true
...
}
复制代码
同时为了 Kotlin 可以正常使用 databinding 相关的注解,须要同时在 build.gradle
中引入相应插件:git
apply plugin: 'kotlin-kapt'
android {
dataBinding.enabled true
...
}
复制代码
咱们知道,在 databinding 中,咱们常常会使用 BindingAdapter 来为 widget 添加更多的自定义属性,从而以更丰富的手段来将数据绑定到 widget 上。github
通常地,好比咱们在为 View 设置可见性时,以 Java 编写的话,会有以下的代码:设计模式
public class MyBindingAdapter() {
@BindingAdpater("visible")
public static setVisible(View v, boolean visible) {
v.setVisibility(visible ? View.VISIBLE : View.GONE);
}
}
复制代码
用 Koltin 编写的话,要省事许多:app
@BindingAdapter("visible")
fun setVisible(v: View?, visible: Boolean) {
v?.visibility = if (visible) View.VISIBLE else View.GONE
}
复制代码
或者能够直接将该方法做为控件的扩展方法:gradle
@BindingAdapter("visible")
fun View.setVisible(visible: Boolean) {
this.visibility = if (visible) View.VISIBLE else View.GONE
}
复制代码
Kotlin 编写的话,不须要多余的类,也不须要多余的静态声明,同时更具备可读性。优化
咱们知道,对于绑定到 layout 中的数据,在更新以后,须要调用 notifyPropertyChanged
来触发 UI 更新。ui
好比下面这样一个 layout,引用了两个数据字段:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="id" type="Integer" />
<variable name="name" type="String" />
</data>
<FrameLayout></FrameLayout>
</layout>
复制代码
对应的 Model 的实现,Java 形式以下:
public class UserVM extends BaseObservable {
private int id = 0;
private String name = "";
@Bindable
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
notifyPropertyChanged(BR.id);
}
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
}
复制代码
Kotlin 利用 Property
的语法糖能够稍微简洁一些:
class UserVM : BaseObservable() {
@get:Bindable
var id = 0
set(id) {
field = id
notifyPropertyChanged(BR.id)
}
@get:Bindable
var name = ""
set(name) {
field = name
notifyPropertyChanged(BR.name)
}
}
复制代码
但不少时候,咱们不想作到单独每一个字段都在 <data></data>
中去声明一次,更多地,咱们想绑定 UserVM
便可,其字段能够用诸如 @{user.id}
或 @{user.name}
等表示:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="com.packagename.appname.vm.UserVM" />
</data>
<TextView android:width="wrap_content" android:height="wrap_content" android:text="@{user.name}"/>
</layout>
复制代码
这样一来,notify UserVM
更新就不能让 layout 中使用到 name
字段的 UI 更新,因此在这种场景下咱们通常会使用 ObservableField
来单独对字段小粒度实现:
public class UserVM extends BaseObservable {
public ObservableField<Integer> id = new ObservableField<>(0);
public ObservableField<String> name = new ObservableField<>("");
}
复制代码
只要直接修改 ObservableField
的值,就能够触发 UI 更新:
id.set(10);
name.set("Bob");
复制代码
上面的实现以 Kotlin 编写的话,以下:
class UserVM : BaseObservable() {
var id = ObservableField(0)
var name = ObservableField("")
}
复制代码
修改的话,跟 Java 相似:
id.set(10)
name.set("Bob")
复制代码
这时候有人问了,“啊那这样 layout 那边拿到的不是 ObservableField
类型的吗,那是否是 widget 在使用的时候,是否是会自动执行一次 toString
将对象转换成字符串,那这样返回的就是对象的 hash 值了,会有问题的。”
其实否则,databinding 在这块对 ObservableField
作过处理,存在 box
和 unbox
的行为,就有点像 Integer
对象和 int
同样,读者有兴趣的话,能够自行再去深刻了解下。
留意到,每一个字段都得写长长的 ObservableField
和无谓的默认值,这是一个可优化的地方。
建立 ComOb
类,继承 ObservableField
,同时实现几个较经常使用的类型:
open class ComOb<T>(defaul : T?) : ObservableField<T>() {
class String(default: kotlin.String = "") : ComOb<kotlin.String>(default)
class Int(default: kotlin.Int = 0) : ComOb<kotlin.String>(default)
class Boolean(default: kotlin.Boolean = false) : ComOb<kotlin.Boolean>(default)
}
复制代码
这样的话,咱们在声明时,就能够更加简洁:
class UserVM : BaseObservable() {
var id = ComOb.Int()
var name = ComOb.String()
}
复制代码
同时,咱们留意到,Kotlin 对于 ObservableField
的值的修改方式,仍是不够 Kotlin 化。诸如 Java 中的 view.setVisibility(xxx)
在 Kotlin 中已经被统一改造为 view.visibility = xxx
。Kotlin 的设计是更偏向于属性驱动,而非事件驱动。 //我的理解,不喜勿喷 :)
那么,咱们有没有改造的可能?是有的,借助 Kotlin Property
的特性,咱们能够作到:
open class ComOb<T>(defaul : T?) : ObservableField<T>() {
var value: T? = default
set(value) {
field = value
this.set(value) //注意,这个this.set才是ObservableField原有的方法,即咱们以前直接调用的方法
}
class String(default: kotlin.String = "") : ComOb<kotlin.String>(default)
class Int(default: kotlin.Int = 0) : ComOb<kotlin.String>(default)
class Boolean(default: kotlin.Boolean = false) : ComOb<kotlin.Boolean>(default)
}
复制代码
这里的作法有点 tricky,是在 ComOb
中制造了一个“傀儡”属性 value
,而后将其以 property 的形式暴露出去。
这样一来,咱们就能够经过修改 value 来修改 ObservableField
的内部数值(以修改value
的间接方式):
class UserVM : BaseObservable() {
var id = ComOb.Int()
var name = ComOb.String()
fun foo() {
id.value = 10 //至关于 id.set(10)
name.value = "Bob" //至关于 name.set("Bob")
}
}
复制代码
话就说这么多了,很久没写文章,后续但愿可以多将实际开发和优化中遇到的问题和解决办法分享给你们。
嗯?因此跟鸡你太美有什么关系???
我的博客:mindjet.github.io
最近在 Github 上搞事的项目:
欢迎 star(唱)/ fork(跳)/ issue(rap)/ PR(篮球)