Kotlin+DataBinding的使用以及和Vue的比较

随着移动应用前端化越来越严重,原生应用开发的比重逐渐降低,慢慢被微信小程序/ReactNative/Weex/H5+/混合应用等替代,而这些前端化的技术栈中,mvvm模式最受推崇。
google在2015年的I/O大会就推出了mvvm模式的DataBinding框架,而在实际项目中被使用的情况并不多,在前端技术快速发展的今天,mvvm模式被推向了风口浪尖,而Android的DataBinding又一次受到了关注。
在google的2017年I/O大会上,kotlin被指定为Android开发官方语言,本篇以一个小例子记录DataBinding在kotlin环境下的配置以及使用,以及和前端框架Vue的mvvm模式的比较。

mvvm示例

建议以了解MVVM模式为前提阅读本篇

添加工程依赖

新建一个新的基于kotlin的Android工程后,需要在gradle的配置文件中增加一些配置和依赖才能使用DataBinding。

在项目的build.gradle文件中,先把plugin的版本号抽出来,作为一个全局变量,便于配置使用

buildscript {
    ext.kotlin_version = '1.1.51'
    ext.android_plugin_version = '3.0.1' //把plugin版本号定义一个变量
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:$android_plugin_version" //使用变量代替原版本号
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}
...

在app的build.gradle文件中增加配置

...
android {
	...
    dataBinding {
        enabled = true
    }
}
dependencies {
	...
    kapt "com.android.databinding:compiler:$android_plugin_version"
}

kapt {
    generateStubs = true
}

配置完成后,这个Android工程已经支持DataBinding框架了。

XMl文件处理

为了演示方便,先调整下初始工程的MainActivity对应的布局文件activity_main.xml的内容

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="默认值" />
  
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

这是一个非常简单的布局,包含一个TextView和一个EditText,下面以编辑EditText的内容同步更改TextView的内容为目的来使用DataBinding对xml进行改造。

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context="top.nightfarmer.databindingdemo.MainActivity">
    <data>
        <variable
            name="user"
            type="top.nightfarmer.databindingdemo.User" />

    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.name, default=默认值}" />
        <EditText android:addTextChangedListener="@{user.nameWatcher}"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{user.name}" />
      </LinearLayout>
</layout>

调整到的地方有: - 布局文件的根标签改为了<layout>...</layout>, - 增加了<data>...</data>来声明DataModel变量 - 使用@{...}语法替换布局中原有常量 - 使用android:addTextChangedListener="@{user.nameWatcher}"进行EditText数据的双向绑定

<data>...</data>声明了一个叫做user的变量,并指定了user的类的定义,这里的user只是声明变量,并没有实例化,变量的实例化过程并不是在xml文件中,而是在绑定xml文件的代码中进行的。

定义ViewModel类

正常情况下,定义一个kotlin类是如下格式

    class User {
        var name: String? = null
    }

在本次的DataBinding场景中,需要改造进行一些改造

class User {
    var name = ObservableField<String>()

    val nameWatcher = object : TextWatcher {
        override fun afterTextChanged(s: Editable) {
            if (s.toString() != name.get()) {
                name.set(s.toString())
            }
        }
        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
        }
        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
        }
    }
}

这个类变得复杂了很多,name的类型改为了名为ObservableField的包装类,通过name.get和name.set方法来对name的值进行修改,达到监听的目的。
增加了名为nameWatcher的监听对象,在监听回调中,判断值的变更并对name变量进行赋值。

调整Activity

在常规模式中,我们使用setContentView(R.layout.activity_main)来指定Activity的布局文件,而使用DataBinding时,我们要使用下面的代码来替换:

        val activityMainBinding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
        activityMainBinding.user = User()

DataBinding框架根据activity_main.xml文件自动生成了名为ActivityMainBinding的绑定类,通过根据布局文件实例化的绑定类,可以给DataModel变量user进行实例化。

小结

经过上述的调整,这个小功能形成了一个完整的闭环: 1. Activity中根据布局文件创建布局View,并通过Binding给xml中的user变量赋值 2. 布局中通过android:text="@{user.name, default=默认值}"语法监听并更新text 3. 布局中通过android:addTextChangedListener="@{user.nameWatcher}"监听EditText组件的内容变更,并更新user对象的name属性,name属性变更后会通知所有使用@{user.name}语法的控件进行更新

这是一个非常简单的DataBinding示例,更高级的特性不会在这里展示。
为什么呢?因为笔者并不会在实际项目中使用它,那么为什么呢?通过如两个对比来说明原因。

与Kotlin+anko对比

在app的build.gradle文件中增加anko的依赖

implementation "org.jetbrains.anko:anko:0.10.5"

xml文件保持常规写法不变
user类文件保持常规写法不变
Activity保持常规写法不变
要实现这个效果,只需要在Activity中加入5行代码

et_demo1.textChangedListener {
	onTextChanged { charSequence, _, _, _ ->
		tv_demo1.text = charSequence
	}
}

于Vue对比

或许拿Android的框架和前端框架对比并不怎么合适,但同样是MVVM模式,我或许更喜欢Vue一些?

<template>
    <div>
        <div>{{name}}</div>
        <input v-model="name"/>
    </div>
</template>

<script>
    export default {
        data: function () {
            return {
                name: '默认值'
            };
        }
    };
</script>

嗯 清爽!

总结

DataBinding的诞生或许只是为了MVVM而MVVM,也或许DataBinding是在2015年设计的所以并没有Vue这种清爽的形式。
这个示例只是实现一个简单的功能,DataBinding更高级的特性并没在这里展示,DataBinding对代码的入侵性太强,常规开发中从网络获取数据是直接反序列化为实体类并进行展示的,而DataBinding则不能正常反序列化,还需要手动转换。

总之笔者认为在项目中引入DataBinding的弊大于利,在越来越多开发者从Java阵营转入kotlin阵营的形势下,DataBinding显得较为臃肿。

在日常开发中或许kotlin+anko和ReactNative比DataBinding更加方便。

文章目录
,