Data Binding在RecyclerView里的应用

关于Data Binding

Data Binding 是谷歌发布的一个MVVM的框架,使用它可以避免许多“findviewbyid”这样的冗余代码,大大提高了编码效率。大家都知道,MVVM即Model-View-ViewModel,Model的作用是提供数据,而View负责显示数据,ViewModel作为桥梁把前面进行双向绑定。当然,它的使用有对IDE有一定的要求,Android Studio的gradle版本不低于1.5.0-alpha1,系统版本要大于2.1。

RecyclerView是谷歌官方推出的用来取代ListView的一个组件,有着比后者更灵活和可控的特性。本文主要讲解Data Binding在到RecyclerView里的具体应用。

预备工作

build.gradle里加入如下代码,起用Android对Data Binding的支持

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

同样在build.gradle里添加对RecyclerView的依赖

1
2
3
dependencies {
compile 'com.android.support:recyclerview-v7:24.1.1'
}

新建一个POJO

让新建的People类继承BaseObservable后,不用再写各个属性的setter()和getter()。当然,如果你不嫌累,更喜欢用传统的方法,
那就不用继承BaseObservable,按照原来的方法写代码就行。

1
2
3
4
5
6
public class People extends BaseObservable {
public final ObservableField<String> name = new ObservableField<>();
public final ObservableField<String> gender = new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}

布局文件

  • item_people

LinearLayout里的代码和原来的一样,只不过使用Data Binding,需要在外面加上< layout > 标签,在< data >里定义变量,可以使用import引入包。
type后跟包名。在TextView可以这样使用变量值 android:text=”@{people.name}” 。当引入包有类名冲突时,可以加alias以区分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="com.hui.databingding_recyclerview.People"/>
<variable name="people" type="People"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{people.name}"
android:textColor="#000"
android:layout_marginLeft="15dp"
android:textSize="16sp"/>
...
</LinearLayout>
</layout>

:控件引用变量值时,变量后面的属性要保证类型正确,非String类型要转换成String类型。比如People的age属性为int类型,如果代码这样写android:text=”@{people.age}”,会引起类似android.content.res.Resources$NotFoundException: String resource ID #0x19 的异常。
正确的代码应为android:text=’@{“”+people.age}’

  • activity_main
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"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="com.hui.databingding_recyclerview.MainActivity"/>
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.hui.databingding_recyclerview.MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_people"
android:layout_width="match_parent"
android:layout_height="wrap_content"></android.support.v7.widget.RecyclerView>
</RelativeLayout>
<layout>

自定义Adapter

建新一个MyAdapter类,继承RecyclerView.Adapter

1
2
3
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
}

建新内部类ViewHolder。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
...
static class ViewHolder extends RecyclerView.ViewHolder{
ItemPeopleBinding binding = null;
public ViewHolder(ItemPeopleBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public void bindto(People people) {
binding.setPeople(people);
binding.executePendingBindings();
}
}
...

实现RecyclerView.Adapter 的onCreateViewHolder()、onBindViewHolder()和getItemCount()方法。
ItemPeopleBinding是由系统根据itemt_people的xml文件自动生成的。通过bind.getRoot() 可获取对应的View对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
...
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
ItemPeopleBinding binding = ItemPeopleBinding.inflate(inflater,parent,false);
binding.getRoot().setOnClickListener(this); //监听点击事件
return new ViewHolder(binding);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.bindto(peoples.get(position));
holder.binding.getRoot().setTag(peoples.get(position)); //方便在别的地方调用
}
@Override
public int getItemCount() {
if (peoples == null)
return 0;
return peoples.size();
}
...

自定义点击事件

首先,定义一个接口用来回调。由于RecyclerView没有提供点击事件的接口,需要自己实现,这里让MyAdapter类实现View.OnclickListener用来添加自定义的点击事件。通过View的SetOnClickListner()传入this,因为MyAdapter已经实现了OnClickListener接口,在MyAdapter中的OnClick()方法里调用自定义接口的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> implements View.OnClickListener{
...
private OnRecyclerViewItemClickListener mOnItemClickListener = null;
public interface OnRecyclerViewItemClickListener {
void OnItemClick(View view, People people);
}
@Override
public void onClick(View v) {
if(this.mOnItemClickListener != null) {
mOnItemClickListener.OnItemClick(v,((People) v.getTag()));
}
}
public void setOnItemClickListener(OnRecyclerViewItemClickListener onItemClickListener) {
this.mOnItemClickListener = onItemClickListener;
}
...

MainActivity

先获取ActivityMainbinding对象,它是系统根据activity_main.xml生成的

1
2
3
4
5
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
...

初始化一个队列

1
2
3
4
5
6
7
8
9
private List<People> peoples = new ArrayList<People>();
for (int i=1; i<=10; i++) {
People people = new People();
people.name.set("Jim" + i);
Random random = new Random();
people.gender.set(random.nextBoolean() ? "male":"female" );
people.age.set(20 + random.nextInt(10));
peoples.add(people);
}

建新一个MyAdapter对象,并实现OnRecyclerViewItemClickListener()

1
2
3
4
5
6
7
8
private MyAdapter adapter = new MyAdapter(peoples);
adapter.setOnItemClickListener(new MyAdapter.OnRecyclerViewItemClickListener() {
@Override
public void OnItemClick(View view, People people) {
Toast.makeText(MainActivity.this, "你点击的是:" + people.name.get(), Toast.LENGTH_SHORT).show();
}
});

设置adapter

1
2
3
4
LinearLayoutManager layoutManager = new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,
false);
binding.recyclerView.setLayoutManager(layoutManager);
binding.recyclerView.setAdapter(adapter);

Model通过ViewModel(自定义的类,这里是People)与View绑定后,每次数据发生变动后,View会自动地同步更新。这样,即使在主线程以外的线程里更新数据,不必通知主线程,View也能接收到通知自动更新。

源码