hello android databinding!

0x00 修改gradle文件

  • 整个项目的bulid.gradle
1
2
3
4
5
6
7
8
9
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.5.0'
classpath "com.android.databinding:dataBinder:1.+"
}
}
  • 单个模块的bulid.gradle
1
2
apply plugin: 'com.android.application'
apply plugin: 'com.android.databinding'

0x01 格式

在原有的布局文件中,加上一层layout标签包裹住,需要放置的数据,则放在data标签里。

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="field" type="com.example.type"/>
</data>
<!-- here is the old layout-->
<LinearLayout />
<!-- here is the old layout-->
</layout>

note:
截止至2016-04-01,在目前的Android Studio IDE1.5.1以及dataBinder1.0-rc5版本中,还没有办法做到xml文件里,data标签里,包名,类名的自动补全

0x02 数据绑定和事件绑定

  • layout.xml如下,对于type属性,如果该类属于java.lang包的话,可以省去包名,如StringInteger等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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"/>
<variable name="listener" type="com.example.Listener"/>
</data>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="@{user.name}" />
<Button
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:onClick="@{listener.onClick}" />
</LinearLayout>
</layout>
  • com.example.User.java如下
1
2
3
4
5
6
public class User {
private final String name;
public User(String name) {
this.name = name;
}
}
  • com.example.Listener.java如下
1
2
3
4
public class Listener {
// 注意这里的onClick方法需要将View作为参数传入
void onClick(View view);
}
  • 每一个使用dataBinding的layout文件,都会被编译产生一个对应的类,这个类的位置在build/intermediates/classes/debug/packageName/databinding/xxxBinding.java中,并且遵循驼峰命名法

    如 main_activity.xml –> MainActivityBinding.java

  • 其中带有ID的控件,都会对应生成一个变量,同样遵循驼峰命名法

    如 btn_register –> btnRegister

  • 基于<variable name="user" type="com.example.User"/>这样子的声明,生成的xxxBinding.java会有一个setUser(User user)方法。

  • 在Activity里这样子调用和绑定

1
2
3
4
5
6
7
8
9
@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);
binding.btnRegister.setText("xxx");
}
  • 在fragment中,可以在onCreateView()方法中进行数据绑定
1
2
3
4
5
6
7
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
FragmentLoginBinding dataBinding =
DataBindingUtil.inflate(inflater, R.layout.fragment_login, container, false);
// something initial
return dataBinding.getRoot();
}

0x03 BindAdapter的使用

对于非控件自带的attr,使用BindAdapter注解来实现,以一个根据URL加载头像的场景为例。

写一个ViewModel

1
2
3
4
5
6
7
public class ImageViewModel {
@BindAdapter("bind:url")
public static final void loadAvatar(ImageView img, String avatarUrl) {
// load image by avatarUrl
loadAvatarByUrl(img, url);
}
}

在布局文件中加入注解中绑定的url属性(需要加入appNS)

1
2
3
4
5
6
<ImageView
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:url="@{user.avatarUrl}"
/>

再也不用在每一个需要用到头像的地方,都写类似下面的代码了。

虽然loadAvatarByUrl(img, url)的逻辑可能并不复杂,但是使用DataBinding胜在将加载头像的逻辑完全从Activity或者Fragment中剥离出来

1
2
ImageView img = (ImageView)findViewById(R.id.img);
loadAvatarByUrl(img, url);

0x04 DataBinding在RecyclerView的使用

dataBinding对RecyclerView的支持也非常好,用法很简便。

首先在原来的item布局文件中,加入<layout><data>等标签。

接着,在ViewHolder中声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyViewHolder extends RecyclerView.ViewHolder {

private MyItemBinding binding;

public ExprHolder(View itemView) {
super(itemView);
// 这样即可完成绑定
binding = DataBindingUtil.bind(itemView);
}

public MyItemBinding getBinding() {
return binding;
}
}

onCreateViewHolder回调方法中,将LayoutInflater加载进来的View作为构造函数传递给ViewHolder

1
2
3
4
5
6
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item, parent, false);
return new MyViewHolder(view);
}

onBindViewHolder回调方法中,完成数据绑定

1
2
3
4
5
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
Object obj = Objects.get(position);
holder.getBinding().setObj(obj); // 完成数据绑定
}

0x05 其他用法

  • 空值判断,使用??运算符进行空值判断,以下表达式的含义为,

    • 在TextView上,显示user的name字段,
    • user的name字段为空的话,显示user的phone字段,
    • 如果user的phone字段为空的话,显示User的静态常量NO_LOGIN,作一个未登录的说明。
1
2
3
4
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{user.name ?? user.phone ?? User.NO_LOGIN}" />

原生代码对比:

1
2
3
4
5
6
7
8
9
10
TextView tv = (TextView)findViewById(R.id.tv);
if ("".equals(user.getName())) {
if ("".equals(user.getPhone())) {
tv.setText(User.NO_LOGIN);
return;
}
tv.setText(user.getPhone());
return;
}
tv.setText(user.getName());
  • 使用import标签引用一个包含有静态方法的辅助类
1
2
3
4
5
6
7
8
9
10
<data>
<variable name = "mInteger" type = "int"/>
<import type = "com.xxx.XxxHelper" />
</data>

<!-- 在布局中可以这样子调用,就可以在java代码里省下findViewById,在if else中setVisibility()...了 -->
<TextView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:visibility="@{XxxHelper.hasPermisson(mInteger) ? View.VISIBLE:View.GONE}"/>
  • 使用资源文件
1
2
3
4
5
6
7
8
9
10
11
<data>
<variable name="mode" type="int">
<import type="com.config.Constants" />
</data>
<!-- 在布局文件中引用资源文件 -->
<Button
android:id="@+id/btn_register"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{mode == Constants.MODE_REGISTER? @string/btn_register : @string/reset_password}" />

com.config.Constants这样子写

1
2
3
4
public class Constants {
public static final int MODE_REGISTER = 0;
public static final int MODE_RESET_PASSWORD = 1;
}
  • 别名 如果import标签引用到的两个类名相同,可以使用alias属性
1
2
3
4
<data>
<import type="com.package.one.Klass">
<import type="com.package.two.Klass" alias="KlassTwo">
</data>

0x06 开发过程中遇到的坑

  1. 在写xml文件的时候,由于插件的支持有限,有关dataBinding这一部分没有自动补全和提示,有时候编译的时候报错说xxxDataBinding类不存在之类的信息,一定是手抖写错了表达式,只要将Android Studio的message窗口(就是那个查看编译反馈的窗口)拉到最下面,一般都可以看到是哪一个布局文件中表达式写错了,修改对应的表达式即可。

  2. 由于dataBinding插件是实时编译的,有时候会出现以下情况,修改了某个布局文件的名字,或者其中控件的ID,也会报错,这时候只要找到对应生成的java文件删除,让插件自动重新编译即可。对应的java文件位置在:build/intermediates/classes/debug/packageName/databinding/xxxBinding.java,或者删除整个build文件夹也可。