Android DataBinding实践

January 27, 2016

前言

最近在新项目中采用了MVVM + DataBinding + RxJava的架构,后面会把一些知识点都记录下来,这篇先来说说DataBinding

之前使用MVP模式时,ViewPresenter之间总要定义一个介面IViewPresenter必须通过这个IView来操作View。但是现在使用了MVVM + DataBindingViewModel通过DataBinding机制将Model塞给View,无需这个IView了,而且再也不需要findViewById()或者ButterKnife这样的注入框架了,使用下来确实少写很多代码。

DataBinding允许你直接在XML布局中绑定对象,并且可以监听对象的变化以便及时更新UI,但是现在只能单向绑定,比如你给EditText绑定了一个String,该String的变化会及时显示在EditText,但是你在EditText中输入的内容不会直接赋值到String中。现在IDE对DataBinding支持一般,没有代码提示,毕竟推出不久,估计Android Studio 2.0之后会好些。

示例

我用V2EX的API写了个简单的项目用来示例DataBinding的一些用法,采用了MVVM + DataBinding + RxJava

集成

在项目中集成DataBinding很容易,只要你的Android Studio版本>=1.3以及Android的Gradle插件版本>=1.5,然后在build.gradle中加入如下配置即可:

android {
    ....
    dataBinding {
        enabled = true
        // version = 1.1 // 可选,建议最好不加,使用Gradle插件默认的版本
    }
}

gradle插件会帮你下载DataBinding相关的依赖包,1.5版本的gradle插件默认集成的是1.0-rc5版本的DataBinding,version选项经实验最好不加,使用当前Gradle插件默认的DataBinding版本即可,否则会有些莫名奇妙的编译问题,比如我遇到这个问题。可以从jcenter上查看到DataBinding的最新版本。

一些用法

Layout相关

  • 除了java.lang.*包下的类,其他的类要么使用全名,要么需要import
<import type="android.view.View"/>
  • 可以在xml中使用静态方法:
android:text="@{Html.fromHtml(mainItem.content_rendered), default=无内容}"
  • 自定义Binding类名字,默认根据xml的名字生成
<data class="MainListBinding">
    ...
</data>
  • Include

如果xml中包含include标签,可以如下方式传递:

// activity_main.xml
<include
	id="@+id/empty_layout"
 	layout="@layout/empty_content"
 	app:isShowContent="@{viewModel.isShowContent}" />
 	
// empty_content.xml
<data>
   <variable
       name="isShowContent"
       type="boolean"/>
</data>

如果你需要引用被include布局中view元素,include标签的id属性不可少。

  • xml中带id属性的views都会在binding类中生成一个public final类型的引用,引用名即id属性值:
<TextView
	android:id="@+id/tv_username"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"/>
	
// 生成 public final android.widget.TextView tvUsername;

这样我们代码中就不需要findViewById()了,直接binding.tvUsername即可引用。

  • 空指针问题
android:text="@{mainItem.title, default=无标题}"/>

如果mainItem为空,不会报错,text的值是mainItem.title的默认值null

Observable

要想让对象的变化及时通知到view,则该对象必须实现Observable接口或者是一个ObservableFields

Adapter中的绑定

因为RecyclerView或者ListView等每个item的布局不一定相同,可能会产生多个Binding类,这种情况可以使用Binding的基类ViewDataBinding,然后在bind data到每个view时,如果data相同,则可使用setVariable(),如果data不同,可以根据每个view type的不同,强制转换ViewDataBinding至具体的Binding类,再调用其setXxx方法。可参见示例。

绑定变量的变化会自动调用属性的setter方法

比如TextViewandroid:text其实是去调用了其setText(String)方法,注意参数类型的匹配,因为可能有多个重载方法。而且哪怕一个控件并没有相应的属性,但是有setter方法,我们仍然可以这么写,比如示例中:

<android.support.v4.widget.SwipeRefreshLayout
  android:id="@+id/refresh_layout"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  app:refreshing="@{viewModel.isRefresh}"/>

SwipeRefreshLayout控件并没有refreshing这个属性,但是却有setRefresh(boolean)方法,用来控制刷新状态。可以点击菜单上的刷新来测验这个写法。

@BindingAdapter

因为并不是所有属性都有setter方法,这时候就需要用这个注解来自定义属性的逻辑。比如示例中:

<android.support.v7.widget.RecyclerView
	android:id="@+id/list_hot"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	app:contentList="@{viewModel.contentList}"/>

RecyclerView并没有contentList这个属性而且没有setContentList()方法,所以需要我们自定义:

@BindingAdapter("bind:contentList")
public static void setContentList(RecyclerView view, List<Topic> list) {
   MainAdapter adapter = (MainAdapter) view.getAdapter();
   adapter.addRows(list);
}

这样每次刷新时只要将返回数据set给viewModel.contentList就会调用上面的方法刷新adapter了。官网文档还有更详尽的@BindingAdapter例子。

总结

详细的文档还是直接参考官网吧,有的写法比如@BindingConversion,我的示例中并未用到,后面想到使用场景时再用。我发这篇博客时,最新版本已经到2.0.0-alpha9了,想必会和Android studio2.0一起出正式版,DataBinding能方便的实现MVVM模型,又有官方支持,应该会慢慢流行起来,所以建议可以在项目中尝试了。

Retrofit 2.0的一些用法

Published on January 25, 2016