Data Binding Library是Google在2015年IO大会上发布的一个用于实现MVVM设计模式的支持库
环境配置
在Android Studio 2.0 原生支持Data Binding框架,配置也变得很简单,只需要在gradle中配置如下脚本即可
android {
dataBinding{
enabled = true;
}
}
布局文件
使用Data Binding后,布局文件也跟以前的有所区别
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="android.view.View"/>
<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>
标签 | 说明 |
---|---|
layout | 布局文件的根节点 |
data | 要实现 MVVM 的 ViewModel 就需要把数据(Model)与 UI(View) 进行绑定,data 节点的作用就像一个桥梁,搭建了 View 和 Model 之间的通路 |
variable | 声明变量 |
import | 导包 |
@{ } | 使用定义的变量 |
注意:java.lang.* 包中的类会被自动导入,可以直接使用,例如要定义一个 String 类型的变量:
<variable name="firstName" type="String" />
Includes
<?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>
需要注意的是,Data binding不支持include标签作为merge标签的直接子标签,例如下面的布局是不支持的
<?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>
绑定数据
@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);
}
- 系统会帮我们自动生成一个以xml布局文件名字开始,以Binding结束的类,例如上面的MainActivityBinding.java,另外还会生成一个BR文件,类似于R文件。
- 除了使用框架自动生成的 MainActivityBinding,我们也可以通过如下方式自定义类名
<data class="com.example.CustomBinding">
</data>
use data binding items inside a ListView adapter
1、BaseAdapter
class CommonAdapter<T> extends BaseAdapter{
private List<T> mList;
private Context mContext;
private int layoutId;
private int variableId;
public CommonAdapter(List<T> list, Context context, int lauoutId, int variableId) {
mList = list;
mContext = context;
this.layoutId = lauoutId;
this.variableId = variableId;
}
@Override
public int getCount() {
return mList.size();
}
@Override
public Object getItem(int position) {
return mList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewDataBinding binding = null;
if (convertView == null){
binding = DataBindingUtil.inflate(LayoutInflater.from(mContext),layoutId,parent,false);
}else {
binding = DataBindingUtil.getBinding(convertView);
}
binding.setVariable(variableId,mList.get(position));
return binding.getRoot();
}
}
2、布局文件
ListView的item布局
<?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">
<data>
<variable
name="user"
type="com.google.demo.model.User"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:imageUrl="@{user.icon}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name}"/>
</LinearLayout>
</layout>
<?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">
<data>
<variable
name="adapter"
type="android.widget.BaseAdapter"/>
</data>
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:adapter="@{adapter}"/>
<!--因为ListView有setAdapter()方法,所以这里可以使用app:adapter属性-->
</layout>
3、逻辑代码
LayoutLvBinding binding = DataBindingUtil.setContentView(this, R.layout.layout_lv);
List<User> list = new ArrayList<>();
CommonAdapter<User> adapter = new CommonAdapter<>(list,this,R.layout.lv_item, com.google
.demo.BR.user);
binding.setAdapter(adapter);
Observable Objects
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);
}
}
- BaseObservable:实现数据的更新同步到UI控件上需要实现该接口
- @Bindable注解标记过 的getter 方法会在 BR 中生成一个 entry
- notifyPropertyChanged(BR.firstName)通知View更新
ObservableFields
private static class User {
public final ObservableField<String> firstName =
new ObservableField<>();
public final ObservableField<String> lastName =
new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
ObservableField, ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, and ObservableParcelable
Observable Collections
ObservableArrayMap
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
在布局文件中使用
<data>
<import type="android.databinding.ObservableMap"/>
<variable name="user" type="ObservableMap<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"/>
ObservableArrayList
ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);
在布局文件中使用
<data>
<import type="android.databinding.ObservableList"/>
<import type="com.example.my.app.Fields"/>
<variable name="user" type="ObservableList<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"/>
生成Binding class
所生成的Binding类都继承自ViewDataBinding
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bind(viewRoot, layoutId);
绑定事件
public class MyHandlers {
public void onClickFriend(View view) { ... }
public void onClickEnemy(View view) { ... }
}
<?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>
使用资源数据
android:padding="@{large? (int)@dimen/largePadding : (int)@dimen/smallPadding}"
Null Coalescing Operator
android:text="@{user.displayName ?? user.lastName}"
等价于
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
集合的使用
<data>
<import type="android.util.SparseArray"/>
<import type="java.util.Map"/>
<import type="java.util.List"/>
<variable name="list" type="List<String>"/>
<variable name="sparse" type="SparseArray<String>"/>
<variable name="map" type="Map<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]}"
Custom Setters
@BindingAdapter({"imageUrl"})
public static void loadImage(ImageView iv,String url){
if (url == null){
iv.setImageResource(R.mipmap.ic_launcher);
}else {
Glide.with(iv.getContext()).load(url).into(iv);
}
}
Converters(转换器)
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
<View
android:background="@{isError ? @drawable/error : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
DataBindingComponent
所有被注解@BindingAdapter标记的方法,如果该方法为非静态方法,那么都需要一个Component对象,而所需要的Component对象由DataBindingComponent提供,所以我们需要自定义类实现DataBindingComponent接口
public class Utils {
@BindingAdapter({"imageUrl"})
public static void loadImage(ImageView iv,String url){
if (url == null){
iv.setImageResource(R.mipmap.ic_launcher);
}else {
Glide.with(iv.getContext()).load(url).into(iv);
}
}
}
public class MyComponent implements android.databinding.DataBindingComponent {
private Utils mUtils
@Override
public Utils getUtils() {
if (mUtils == null){
mUtils = new Utils();
}
return mUtils;
}
}
在DataBindingUtil.setContentView()之前设置Component
DataBindingUtil.setDefaultComponent(new MyComponent());