雞你太美之 Kotlin 和 Databinding

全民製做人你們好,我是練習時長兩年半的我的練習生。喜歡唱、跳、Rap、籃球。

編不下去了... 其實就是以前的一些項目採用了 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
    ...
}
複製代碼

BindingAdapter

咱們知道,在 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 編寫的話,不須要多餘的類,也不須要多餘的靜態聲明,同時更具備可讀性。優化

ObservableField

咱們知道,對於綁定到 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 作過處理,存在 boxunbox 的行爲,就有點像 Integer 對象和 int 同樣,讀者有興趣的話,能夠自行再去深刻了解下。

ObservableField 優化

留意到,每一個字段都得寫長長的 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 上搞事的項目:

  • LiteWeather [一款用 Kotlin 編寫,基於 MD 風格的輕量天氣 App],對使用 Kotlin 進行實際開發感興趣的同窗能夠看看,項目中會使用到 Kotlin 的委託機制、擴展機制和各類新奇的玩意。
  • Oros [閒來無事作的守望先鋒英雄展現 App]
  • LiteReader [一款基於 MD 的極輕閱讀 App,提供知乎日報、豆瓣電影等資源],項目主要使用了 MVVM 設計模式,界面遵循 Material Design 規範,提供輕量的閱讀體驗。

歡迎 star(唱)/ fork(跳)/ issue(rap)/ PR(籃球)

相關文章
相關標籤/搜索