DataBinding 快速入门

DataBinding 发布有一段时间了,官方的库也逐渐稳定,这里参考了官方的文档以及一些其他资料,希望对 DataBinding 的使用有一个较为全面的认识。

1.编译环境

首先,需要一个版本号为 1.3.0 以上的 AndroidStudio。
其次,需要在 gradle 脚本中加入一些插件。

1
2
3
4
apply plugin: 'com.android.databinding'

classpath 'com.android.tools.build:gradle:1.5.0'
classpath 'com.android.databinding:dataBinder:1.0-rc4'

到目前为止 gradle plugin 版本是 1.5.0, dataBinder 版本是 1.0-rc4。这里不建议使用加号代替具体的版本号,推荐使用一个具体的版本号。这里提供了代码仓库的网站可供查找库的最新版本 gradle build plugin jcenterdata binding lib。同时这个功能向前兼容到 Android 2.1。

到现在这个东西还在 beta 版本,官方的 guide 的也是有阵子没更新了,文档中提到的 gradle 中要加 databinding 的 enable 选项,现在不需要加了。写这篇文章使用的 Android Studio 1.5,对databinding 的代码提示和 UI 预览还不是很完善。

2016.3.24 update

现在只需在 gradle 中加入 databinding 就可以使用了,之前的 plugin 和 classpath 都不需要了,现在的 databinding 作为 support lib 存在,所以使用之前需要去 SDK Manager 中更新 support 包。

1
2
3
4
5
android {
dataBinding {
enabled = true
}
}

2.绑定布局文件

DataBinding 的作用是自动绑定布局文件和代码中的变量,省去了自己写代码区操作 UI 的普通操作。

2.1 布局文件写法

在布局文件中引入了 data 标签和 variable 标签,用来定义变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
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="@{user.firstName}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"/>
</LinearLayout>
</layout>

variable name 表示变量的名称,type 表示变量类型。然后就可以在 layout 中使用这个变量了。

2.2 绑定的对象

前面绑定的对象是:

1
2
3
4
5
6
7
8
public class User {
public final String firstName;
public final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}

或者写成 JavaBean 的形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class User {
private final String firstName;
private final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return this.firstName;
}
public String getLastName() {
return this.lastName;
}
}

如果是第一种形式,则 android:text 中 调用的 @{user.firstName} 就是 firstName 这个变量,如果是第二种形式,那么调用的就是 getFirstName()。

2.3 开始绑定

接下来就可以开始绑定了,我们的目标是将 Java 中的对象 User 和 layout 中的用到 user 绑定起来:

1
2
3
4
5
6
7
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
User user = new User("Test", "User");
binding.setUser(user);
}

databinding 的插件会根据 layout 的名字生成对应的 binding,例如这里的 layout 是 main_activity,则会生成 MainActivityBinding。当然,这个自动生成的类名可以指定,后面会讲到。
下面几种不同的写法,可以用于 Fragment 中和 listitem 中:

1
2
3
4
5
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());

ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);

2.4 绑定事件

绑定事件算是对之前 android:onClick 属性的加强:

1
2
3
4
public class MyHandlers {
public void onClickFriend(View view) { ... }
public void onClickEnemy(View view) { ... }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="handlers" type="com.example.Handlers"/>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
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="@{user.firstName}"
android:onClick="@{user.isFriend ? handlers.onClickFriend : handlers.onClickEnemy}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"
android:onClick="@{user.isFriend ? handlers.onClickFriend : handlers.onClickEnemy}"/>
</LinearLayout>
</layout>

databinding 增加了对条件表达式的支持,使得绑定事件更加灵活,条件表达式后面有一节会讲到表达式的语法。

3.布局文件进阶

这一部分是 layout 中涉及到的 databinding 更为详细的东西。

3.1 Import

import 的作用和 java 中的 import 一样,默认情况下 databinding 已经包含了 String,所以不用自己 import String,但是别的就需要 import了。

1
2
3
<data>
<import type="android.view.View"/>
</data>

现在配合条件表达式就可以根据对象的状态控制 View 的状态:

1
2
3
4
5
6
<!--beta 版本对条件表达式语法支持还不够好,这里 IDE 会提示错误,但是编译不会报错-->
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult() ? View.VISIBLE : View.GONE}"/>

还可以 import 集合:

1
2
3
4
5
6
<data>
<import type="com.example.User"/>
<import type="java.util.List"/>
<variable name="user" type="User"/>
<variable name="userList" type="List<User>"/>
</data>

还可以导入静态方法:

1
2
3
4
5
6
7
8
9
<data>
<import type="com.example.MyStringUtils"/>
<variable name="user" type="com.example.User"/>
</data>

<TextView
android:text="@{MyStringUtils.capitalize(user.lastName)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

如果类名重复了还可以起别名:

1
2
3
<import type="android.view.View"/>
<import type="com.example.real.estate.View"
alias="Vista"/>

3.2 变量

variable 标签需要在 data 下使用:

1
2
3
4
5
6
<data>
<import type="android.graphics.drawable.Drawable"/>
<variable name="user" type="com.example.User"/>
<variable name="image" type="Drawable"/>
<variable name="note" type="String"/>
</data>

这些变量会在编译期受到检查,如果是 Observable 或者是 Observable Collections 对象,则会变成双向绑定,从名字可以看出来是通过观察者实现的。如果只是一个普通的对象,那么则是一个单向绑定。

当相同的 layout 文件使用同样的变量名时会冲突。例如横屏和竖屏是两个 layout 文件夹,但是里面要显示的内容相同,这就需要起不同的变量名,因为 databinding 会将同一个 layout 文件夹下的变量合并到一个文件里。

3.3 自定义绑定类名

如果对自动生成的类名不满意,可以自定义类的名称:

1
2
3
<data class="ContactItem">
...
</data>

这样写的话会生成的类会放到 {$packagename}.databinding 的目录中,例如 com.example.databinding。
如果是下面这种加个点,则会放到包名目录下,例如 com.example。

1
2
3
<data class=".ContactItem">
...
</data>

当然也可以使用包名加类名的形式:

1
2
3
<data class="com.example.ContactItem">
...
</data>

3.4 布局文件的 include

variable 可以通过 include 标签传递传递到他的 layout 中,首先要引入 bind 域,然后 bind 之后跟的一个变量的名字,要求当前 xml,include 的 layout 要有同样的名字的 variable,bind 跟的名字也要一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/name"
bind:user="@{user}"/>
<include layout="@layout/contact"
bind:user="@{user}"/>
</LinearLayout>
</layout>

绑定不支持根标签为 merge 的布局,例如下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable name="user" type="com.example.User"/>
</data>
<merge>
<include layout="@layout/name"
bind:user="@{user}"/>
<include layout="@layout/contact"
bind:user="@{user}"/>
</merge>
</layout>

3.5 表达式语法

支持语法和 Java 相同:

  • Mathematical + - / * %
  • String concatenation +
  • Logical && ||
  • Binary & | ^
  • Unary + - ! ~
  • Shift >> >>> <<
  • Comparison == > < >= <=
  • instanceof
  • Grouping ()
  • Literals - character, String, numeric, null
  • Cast
  • Method calls
  • Field access
  • Array access []
  • Ternary operator ?:
1
2
3
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age &lt; 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'

不支持的语法:

  • this
  • super
  • new
  • Explicit generic invocation (不支持泛型方法)

例如:

1
2
3
4
5
public class StringUtils {
public static <T> String generic(T t){
return t.toString();
}
}
1
2
3
4
5
6
7
8
<TextView
android:text="@{StringUtils.<User>generic(user)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text="@{StringUtils.generic(user)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

这些调用泛型的方法会在编译期报错,如果想调用的话可以这样做:

1
2
3
4
5
6
7
8
public class StringUtils {
public static <T> String generic(T t){
return t.toString();
}
public static String g(Object o){
return generic(o);
}
}
1
2
3
4
<TextView
android:text="@{StringUtils.g(user)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

判断是否为 null

1
2
3
android:text="@{user.displayName ?? user.lastName}"
<!-- 两句等价 -->
android:text="@{user.displayName != null ? user.displayName : user.lastName}"

属性引用

1
android:text="@{user.lastName}"

如果 user 为 null,那么这个 text 将会是 null,但是不会报错,如果是基本类型,则会和 java 中一样给一个默认初始值,例如 int 的话会是0。

集合
先上示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<data>
<import type="android.util.SparseArray"/>
<import type="java.util.Map"/>
<import type="java.util.List"/>
<variable name="list" type="List&lt;String>"/>
<variable name="sparse" type="SparseArray&lt;String>"/>
<variable name="map" type="Map&lt;String, String>"/>
<variable name="index" type="int"/>
<variable name="key" type="String"/>
</data>

android:text="@{list[index]}"

android:text="@{sparse[index]}"

android:text="@{map[key]}"

写法比较简单,需要注意的是 &lt; 代表了左括号,前面表达式语法里也有用到,目前不知道为啥要写成这样,但是如果写成 < 符号编译会报错。Java 中的代码也很简单:

1
2
3
4
5
binding.setList(TEST_STR_LIST);

public void onListChange(View v){
binding.setIndex(new Random().nextInt(TEST_STR_LIST.size()));
}

当通过 binding 修改 index 的值的时候,对应的 text 也会跟着改变。

然后是资源文件:

1
2
3
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"

4.双向绑定

双向绑定使用 Observable 来实现。

4.1 Observable 对象

要继承 BaseObservable,对需要双向绑定的变量的 getter 方法加 Bindable 注解,并且在 setter 方法中通知该变量发生改变。其中 BR 和 R 文件一样,是对绑定对象的一个引用,例如这里的 BR.firstName。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private static class User extends BaseObservable {
private String firstName;
private String lastName;
@Bindable
public String getFirstName() {
return this.firstName;
}
@Bindable
public String getLastName() {
return this.lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
public void setLastName(String lastName) {
this.lastName = lastName;
notifyPropertyChanged(BR.lastName);
}
}

4.2 ObservableField

效果和前面的一样就是写法不太一样:

1
2
3
4
5
6
7
private static class User {
public final ObservableField<String> firstName =
new ObservableField<>();
public final ObservableField<String> lastName =
new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}

databinding 提供了一整套的这种东西:ObservableField,ObservableBoolean,ObservableByte,ObservableChar,ObservableShort,ObservableInt,ObservableLong,ObservableFloat,ObservableDouble,和 ObservableParcelable。他们都继承自 BaseObserable。

4.3 Observable 集合

和上一节类似:

1
2
3
4
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
1
2
3
4
5
6
7
8
9
10
11
12
13
<data>
<import type="android.databinding.ObservableMap"/>
<variable name="user" type="ObservableMap&lt;String, Object>"/>
</data>

<TextView
android:text='@{user["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text='@{String.valueOf(1 + (Integer)user["age"])}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

这样更换不同的 map 就可以更换 TextView 中显示的内容。

除了 map 还有 ObservableArrayList:

1
2
3
4
ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<data>
<import type="android.databinding.ObservableList"/>
<import type="com.example.my.app.Fields"/>
<variable name="user" type="ObservableList&lt;Object>"/>
</data>

<TextView
android:text='@{user[Fields.LAST_NAME]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

这里的 Fields.AGE 是写在 java 代码中的 int 值。另外需要注意的就是 &amplt; 左括号写法。

以上示例代码放到了 github 中,目前来看有一些小问题,例如 import 要放到 data 内的开始,不能和变量混着写,会导致 import 失败进而导致编译错误。

5.绑定Class的生成

binding 类的的生成是自动的,生成的类继承自 ViewDataBinding

下面是一个简单的写法,将 inflater 绑定到对应的 binding 上。

1
2
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);

也可以这样:

1
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

更多的时候是通过 DataBindingUtil 来创建 binding:

1
2
3
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);

5.1 View 和 ID 的绑定

每一个带 id 的 view 都会对应一个 public final 的值,例如:

1
2
3
4
5
<TextView
android:id="@+id/bindViewId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>

则在 java 中则会有对应的 TextView 在 binding 中生成,当然 view 的 id 不是必须的,如果你不准备这样使用,那么就不需要给他加 id,编译的时候会自动生成一个 id 用于 binding 内部使用。
例如会生成如下的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.example.bill.databinding;
import com.example.bill.R;
import com.example.bill.BR;
import android.view.View;
public class ActivityBasicBinding extends android.databinding.ViewDataBinding {

private static final android.databinding.ViewDataBinding.IncludedLayouts sIncludes;
private static final android.util.SparseIntArray sViewsWithIds;
static {
sIncludes = new android.databinding.ViewDataBinding.IncludedLayouts(7);
sIncludes.setIncludes(0,
new String[] {"layout_databinding_basic"},
new int[] {5},
new int[] {R.layout.layout_databinding_basic});
sViewsWithIds = new android.util.SparseIntArray();
sViewsWithIds.put(R.id.bindViewId, 6);
}
// views
public final android.widget.TextView bindViewId;
private final android.widget.LinearLayout mboundView0;
private final com.example.bill.databinding.LayoutDatabindingBasicBinding mboundView01;
private final android.widget.TextView mboundView1;
private final android.widget.TextView mboundView2;
private final android.widget.TextView mboundView3;
private final android.widget.TextView mboundView4;
...
}

从代码中可以看到设置了 id 的 view 会按照 id 来生成对应变量,并且设置为 public,如果没有设置 id,则会自动生成一个 mboundView1 之类的私有成员。

1
binding.bindViewId.setText("bind view id");

5.2 变量的绑定

每一个变量都会生成一个 setter 和 getter 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public boolean setVariable(int variableId, Object variable) {
switch(variableId) {
case BR.user :
setUser((com.example.bill.databinding.model.User) variable);
return true;
case BR.list :
setList((java.util.List<java.lang.String>) variable);
return true;
case BR.index :
setIndex((int) variable);
return true;
}
return false;
}

在 setVariable 方法中统一设置变量的值。

5.3 ViewStub

xml 的写法还是和以前一样:

1
2
3
4
5
<ViewStub
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/view_stub"
android:layout="@layout/layout_databinding_basic"/>

binding 会生成一个对应 id 的 ViewStubProxy。然后再需要 inflate 的时候使用 proxy 获取到真正的 viewstub:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
binding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub stub, View inflated) {
LayoutDatabindingBasicBinding b = DataBindingUtil.bind(inflated);
User u = new User("Lei", "Feng");
b.setUser(u);
}
});

public void onShowViewStub(View v) {
if (!binding.viewStub.isInflated()){
binding.viewStub.getViewStub().inflate();
}
}

Android Studio 1.5 对这个支持还比较渣,使用 ViewStubProxy 居然都会报错,好在编译没有问题。

5.4 绑定的高级用法

这部分是其他一些方法的使用,例如在 RecyclerView 中的使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Holder extends RecyclerView.ViewHolder{
ListItemNameBinding binding;

public Holder(View itemView) {
super(itemView);
}

public void setBinding(ListItemNameBinding bind){
this.binding = bind;
}

public ListItemNameBinding getBinding() {
return binding;
}
}

@Override
public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
ListItemNameBinding binding = DataBindingUtil.inflate(LayoutInflater.(parent.getContext()), R.layout.list_item_name, parent, false);
Holder holder = new Holder(binding.getRoot());
holder.setBinding(binding);
return holder;
}
@Override
public void onBindViewHolder(Holder holder, int position) {
holder.getBinding().setUser(userList.get(position));
}

主要是 binding 的传递,并且简化了给 view 的赋值。

6.Attribute Setters

自定义 View 的各种属性。

6.1 Automatic Setters

自动设置算是一个比较常用的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="on attribute click"
app:OnClickListener="@{activity.listener}"/>

</LinearLayout>

<data>

<import type="com.example.bill.databinding.ui.ActivityAttribute"/>

<variable
name="activity"
type="ActivityAttribute"/>
</data>
</layout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ActivityAttribute extends AppCompatActivity {

public View.OnClickListener listener = new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(ActivityAttribute.this,"OPS !!",Toast.LENGTH_SHORT).show();
}
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityDatabindingAttributeBinding bind = DataBindingUtil.setContentView(this, R.layout.activity_databinding_attribute);
bind.setActivity(this);
}
}

首先 Activity 中有一个 listener 变量,在 xml 中拿到 activity 的引用,通过 app 的域来自动绑定对应属性。这个特性的好处在于可以对域(也就是styleable)中没有的属性进行设置,只要有对应的 setter 方法就行。

6.2 Renamed Setters

这个现在貌似没法用,编译不过,以后正式发布了再试下,这里只记录下文档说的方法:

1
2
3
4
5
@BindingMethods({
@BindingMethod(type = "android.widget.ImageView",
attribute = "android:tint",
method = "setImageTintList"),
})

这个注解用于类,BindingMethods 内必须至少有一个 BindingMethod。这个注解可以重定义属性对应的 setter 方法,例如这个例子中 android:tint 对应的方法是 setImageTintList(),而并不是 settint()。

6.3 Custom Setters

这个用法用于将 xml 中自定义的属性和 java code 绑定在一起,而不用写 styleable。下面是一个完整的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class NameCard extends LinearLayout {
private int mAge;

private TextView mFirstName;
private TextView mLastName;

public NameCard(Context context) {
this(context, null);
}

public NameCard(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public NameCard(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}

private void init() {
inflate(getContext(), R.layout.layout_name_card, this);
mFirstName = (TextView) findViewById(R.id.first_name);
mLastName = (TextView) findViewById(R.id.last_name);
}

public void setFirstName(@NonNull final String firstName) {
mFirstName.setText(firstName);
}

public void setLastName(@NonNull final String lastName) {
mLastName.setText(lastName);
}

}

java 类中定义了 setter 和 getter 方法,所以可以直接在 xml 中使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="on attribute click"
app:OnClickListener="@{activity.listener}"/>

<com.example.bill.databinding.NameCard
android:text="my view"
app:firstName="@{@string/firstName}"
app:lastName="@{@string/lastName}"
app:imageUrl="@{imageUrl}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

</LinearLayout>

<data>

<import type="com.example.bill.databinding.ui.ActivityAttribute"/>

<variable
name="activity"
type="ActivityAttribute"/>
<variable
name="imageUrl"
type="String"/>
</data>
</layout>

这里的 app:firstName 和 app:lastName 是会调用 NameCard 中的 setter 方法。而 app:imageUrl 会调用 java code 中的通过 BindingAdapter 绑定的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ActivityAttribute extends AppCompatActivity {

public View.OnClickListener listener = new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(ActivityAttribute.this,"OPS !!",Toast.LENGTH_SHORT).show();
}
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityDatabindingAttributeBinding bind = DataBindingUtil.setContentView(this, R.layout.activity_databinding_attribute);
bind.setActivity(this);
bind.setImageUrl("xxxx");
}

@BindingAdapter({"xxx:imageUrl"})
public static void loadImage(NameCard view, String url) {
Toast.makeText(App.getAppContext(),"load img success",Toast.LENGTH_SHORT).show();
}
}

绑定的方法需要几个注意的地方:

  • 必须是 public static 的方法。
  • 方法名无所谓
  • @BindingAdapter({“xxx:imageUrl”}) 中 xxx 的部分是随意写的,例如可以写成 app:imageUrl 或 bind:xxx:imageUrl 之类的都可以,不必要和 xml 中定义的相同。
  • app:firstName,app:lastName,app:imageUrl 的值必须是引用资源文件或者 java 传的对象,而不能直接写作 app:imageUrl=”xxx”。
  • 方法的第一个参数必须是要绑定的 View 或布局,例如这里要绑定 NameCard 这个 View,就需要把它放到参数的第一个,后面的参数列表顺序是和 BindingAdapter 注解中的顺序是一样的。

7.Converters

类型转换这部分是讲对象的类型是会被自动转换。

7.1 对象的转换

例如,这里的 userMap 的泛型是 <String, Object>,这里 text 属性中拿到的值本来是 object,但是 text 赋值的时候是用 setText(CharSequence cs) 这个方法,所以 object 类型在方法这里就被自动转换为 CharSequence 了。

1
2
3
4
<TextView
android:text='@{userMap["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

7.2 自定义转换

这里 background 需要的一个 Drawable,但是 color 是个 int,所以需要转换成 Drawable。

1
2
3
4
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

转换方法:

1
2
3
4
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}

以上是文档说法,但是在 databinding 1.0rc4 这个版本上尝试的时候,不需要这个 conver 方法就可以成功设置背景,加了这个方法反而设置不成功了。而且打印了 log 也看到确实走了这个方法。

update 2015.12.4
最新版的 Android Studio 这个功能已经正常。

8.总结

本文以及部分 Demo 参考了官方文档MasteringAndroidDataBinding,完整的 Demo 放在了我的 github 上。

update 2015.11.25
在最新的 Android Studio 2.0 preview 中终于可以显示正常而不是提示语法有问题了。