編不下去了... 其實就是以前的一些項目採用了 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(籃球)