鸡你太美之 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(篮球)

相关文章
相关标签/搜索