一、DataBinding使用
<h2 id="1-1">1.使用环境</h2>
DataBinding是一个support library,所以它可以支持所有的android sdk,最低可以到android2.1(API7)。
使用DataBinding需要Android Gradle插件的支持,版本至少在1.5以上,需要的Android studio的版本在1.3以上。
在Android Studio上使用,需要在module级别的build.gradle上添加对DataBinding的支持:
android {
....
dataBinding {
enabled = true
}
}
如果是在library中使用,那么使用使用该library的module也需要在build.gradle添加。
<h2 id="1-2">2.xml布局文件数据绑定</h2>
DataBinding的layout files和普通的非DataBinding布局文件是有一些区别的,下面是一个基础的使用了DataBinding的布局文件:
<?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>
变量user作为被绑定的数据,在layout文件中是这样描述和使用的:
<variable name="user" type="com.example.User"/>
layout中view的属性值通过"@{}"这样的语法表达方式和数据user实现绑定,本例中将TextView的text值设置为user对象的fisrtName了:
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
3.定义数据绑定的Data对象
Data对象官方文档中POJO类和Java Bean都可以,这里我建议使用如下Java Bean:
public class User {
private String firstName;
private 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;
}
}
Note:但是定义一个如上的数据,并不能满足刷新UI的要求,我们需要的Data 还得是一个Observable Data。
DataBinding中有三种不同的数据,object、field、collection。
Observable Objects
Observable是提供添加移除监听的一个java接口,DataBinding基于此接口提供了一个基础类BaseObserable,我们可以这样使用它,通过Bindale注解绑定一个getter,当data属性发生改变在setter中发出通知,这样就实现了响应
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);
}
}
ObseravbleField
google为我们提供了一些Obserable类: ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, and ObservableParcelable。
public static class User {
public final ObservableField<String> firstName =
new ObservableField<>();
public final ObservableField<String> lastName =
new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
ObseravbleCollection
** ObservableArrayMap **
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
在xml中使用:
<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);
xml使用:
<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"/>
4.绑定数据
Android studio会根据layout文件自动生成一个默认的Binding类,类名是根据layout文件名生成的,并有"Binding"后缀结束。例如:activity_main.xml生成的Binding类为ActivityMainBinding,可用如下方式使用Binding类:
@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);
}
启动程序,将会看到user的数据已经在ui中显示了,或者我们也可以这样实现:
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
在ListView或者RecyclerView的adpater中item里使用DataBinding
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
5.事件处理
DataBinding允许我们在xml中view的一些事件属性(如onClick等)中填写DataBinding表达式,也可以通过绑定listener的方式去实现。归纳起来就是:
方法引用和监听绑定,下面介绍着两种方式:
方法引用
public class MyHandlers {
public void onClickFriend(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="@{handlers::onClickFriend}"/>
</LinearLayout>
</layout>
监听绑定
public class Presenter {
public void onSaveClick(Task task){}
}
布局文件
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="task" type="com.android.example.Task" />
<variable name="presenter" type="com.android.example.Presenter" />
</data>
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent">
<Button android:layout_width="wrap_content" android:layout_height="wrap_content"
android:onClick="@{() -> presenter.onSaveClick(task)}" />
</LinearLayout>
</layout>
6.imports
导入类:
<data>
<import type="android.view.View"/>
</data>
现在可以在xml中使用View类的静态资源:
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
也可以为导入类,重新定义一个别名:
<import type="android.view.View"/>
<import type="com.example.real.estate.View"
alias="Vista"/>
下面是个集合的例子:
<data>
<import type="com.example.User"/>
<import type="java.util.List"/>
<variable name="user" type="User"/>
<variable name="userList" type="List<User>"/>
</data>
7.自定义Binding类类名
生成当前目录下的ContactItemBinding
<data class="ContactItem">
...
</data>
也可以通过下面的方式指定生成类存放的目录
<data class="com.example.ContactItem">
...
</data>
8.表达式
支持的运算符:
<ul>
<li>数学运算符: + - / * %</li>
<li>字符串拼接: +</li>
<li>逻辑运算符: && ||</li>
<li>二进制: & | ^</li>
<li>一元运算符: +</li>
<li>位运算符: >> >>> <<</li>
<li>比较: == > < >= <=</li>
<li>instanceof</li>
<li>()</li>
<li>数据类型: character, String, numeric, null</li>
<li>类型转换(ClassCast)</li>
<li>方法回调(Method calls)</li>
<li>数据属性</li>
<li>数组:[]</li>
<li>三元操作符:?</li>
</ul>
列如:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
一些在java中常用而DataBinding xml中不支持的:
<ul>
<li>this
<li>super
<li>new
<li>泛型
</ul>
一个比较有意思的“??”操作符:
android:text="@{user.displayName ?? user.lastName}"
它等于:
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
9.Binding类的其他生成方式
前面,我们提到了一个获取Binding类的方法,我们还可以这样
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
或者:
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
二、DataBinding高级使用
1.动态变量
有时候我们可能不知道Binding类的名称,比如RecyclerView.Adapter中item布局可能有很多,并不会对应特定的Binding类,但是仍然需要通过** onBindViewHolder(VH, int)**去绑定数据,下面的列子是,所有的子布局都有一个"item"变量,通过ViewDataBinding基类去完成绑定:
public void onBindViewHolder(BindingHolder holder, int position) {
final T item = mItems.get(position);
holder.getBinding().setVariable(BR.item, item);
holder.getBinding().executePendingBindings();
}
Immediate Binding
当一个变量被绑定或者绑定的对象发生变化是,DataBinding会让这些改变排队去在下一帧刷险之前改变,有些时候binding效果必须立刻执行,这时候可以使用executePendingBindings()。
Background Thread
只要绑定数据不是一个collection,我们可以在非ui主线程去改变数据,不会有任何线程切换问题,DataBinding会自动处理。
2.Attribute Setters
当一个被绑定的数据的值发生改变时,Binding类会自动寻找该view上的绑定表达式上的方法去改变view,通过google数据绑定框架我们可以去自定义这些方法。
对于一个xml的attribute,data binding会去寻找setAttribute方法,xml属性的命名空间是没有关系的。比如TextView上的一个属性android:text,会去寻找setText(String)。如果表达式返回的是int则会去寻找setText(int),所以必须确保xml中表达式返回正确的数据类型,必要时需要数据转换。我们可以比较容易地为任何属性创造出setter去使用dataBinding。比如support包下的DrawerLayout没有任何属性,但是确有很多setter,下面利用这些已有的setter中的一个:
<android.support.v4.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}"/>
自定义setters
一些xml属性需要自己去定义并实现逻辑,比如android:paddingLeft。但是setPadding(left,top,right,bottom)是存在的,那么我们可以同BindingAdapter注解去自定义个自己的setter:
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
Note:开发者自定义的BindingAdapter和android自带的发生冲突时,data bingding会优先采用开发者自定义的。
多参数的BindingAdapter
@BindingAdapter({"bind:imageUrl", "bind:error"})
public static void loadImage(ImageView view, String url, Drawable error) {
Picasso.with(view.getContext()).load(url).error(error).into(view);
}
BindingAdpater方法可以对属性的旧值和新值进行处理
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int oldPadding, int newPadding) {
if (oldPadding != newPadding) {
view.setPadding(newPadding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
}
事件处理的列子
@BindingAdapter("android:onLayoutChange")
public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue,
View.OnLayoutChangeListener newValue) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
if (oldValue != null) {
view.removeOnLayoutChangeListener(oldValue);
}
if (newValue != null) {
view.addOnLayoutChangeListener(newValue);
}
}
}
3.双向绑定
在xml属性上使用语法"{@=}",
使用该方法就是双向绑定了
<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}"/>
</LinearLayout>
Note:需要注意的是,使用该语法必须要要反向绑定的方法,android原生view都是自带的,所以使用原生控件无须担心,但是自定义view的话需要我们通过InverseBindingAdapter注解类实现,下面是个例子
@InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged")
public static String captureTextValue(TextView view, CharSequence originalValue) {
CharSequence newValue = view.getText();
CharSequence oldValue = value.get();
if (oldValue == null) {
value.set(newValue);
} else if (!contentEquals(newValue, oldValue)) {
value.set(newValue);
}
}
4.Converters
有时候我们想这样写xml属性
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
但是xml属性的setter是一个drawable,我们可以通过BindingConversion实现
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
由于这个注解至是发生在setter层面上,所以并不支持下面的混合写法
<View
android:background="@{isError ? @drawable/error : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
三、关于DataBinding的一些个人看法
1.DataBinding使用心得
使用xml进行view布局
采用符合Java Bean规范的数据原型
规范的自定义View
禁止在BindingAdapter中的setter方法中改变数据或者做数据处理
不建议用BindingConversion处理数据转换
不建议在xml布局中处理view事件
不建议在xml中使用复杂的表达式
2.DataBinding使用的一些思考
DataBinding的不足之处:
1.DataBinding在xml提供了丰富的操作符,但是由于Android studio天生的xml语法检查的贫弱,xml布局中的表达式逻辑错误,不能准确定位,导致debug难度增加,事实上一些BindingAdapter的错误在build的时候也会被提示xml错误。
2.对自定义view的要求比较高,需要自定义绑定方法,如BindingAdapter等。
3.可能由于java 8移除apt,采用了新的API的缘故,所以即使Android Studio2.2已经开始支持java 8特性,但是需要开启jack编译链,DataBinding与之冲突,导致在代码中不能使用lambda表达式等java 8特性。值得欣慰的是,这一问题将在Android Studio2.4中得到解决。
PS:数据绑定的应用软件开发的一种趋势,使用DataBinding的优点显而易见,但是使用的时候我们也需要小心。
网友评论
<variable
name="userInfo"
type="com.fangao.mydatatest.UserInfo"/></data>
public static String captureTextValue(TextView view, CharSequence originalValue) {
CharSequence newValue = view.getText();
CharSequence oldValue = value.get();
if (oldValue == null) {
value.set(newValue);
} else if (!contentEquals(newValue, oldValue)) {
value.set(newValue);
}
}
这个的getText()怎么会是新的值?originalValue没有用到,value又是从哪里来的
···xml
<data class="IBindMapResultPlus">
<variable
name="item"
type="com.saic.roewe.v1.model.MapResult" />
<variable
name="click"
type="android.view.View.OnClickListener" />
<import type="android.view.View" />
<variable
name="distance"
type="String" />
</data>
//参数使用片段
<TextView
android:id="@+id/send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{click}"
android:paddingEnd="@dimen/dp_10"
android:paddingLeft="@dimen/dp_10"
android:paddingTop="@dimen/dimen_8dip"
android:text="@string/send"
android:textAllCaps="false"
android:textColor="@color/white"
android:textSize="@dimen/text_14sp" />
```
报错信息
Error:Execution failed for task ':app:compileDebugJavaWithJavac'.
> java.lang.RuntimeException: Found data binding errors.
****/ data binding error ****msg:Cannot find the setter for attribute 'android:onClick' with parameter type android.view.View.OnClickListener on android.widget.TextView.
file:G:\ASProject\thcc-android-zhusw97-1449\app\src\main\res\layout\item_my_favorite_new_copy.xml
loc:114:39 - 114:43
****\ data binding error ****
****/ data binding error ****msg:Cannot find the setter for attribute 'android:onClick' with parameter type android.view.View.OnClickListener on android.widget.TextView.
file:G:\ASProject\thcc-android-zhusw97-1449\app\src\main\res\layout\item_map_result_plus.xml
loc:99:39 - 99:43
****\ data binding error ****
<import type="com.example.User"/>
<import type="java.util.List"/>
<variable name="user" type="User"/>
<variable name="userList" type="List<User>"/>
</data>
这个也会报错。 type="List<User>"这部分
<import type="android.databinding.ObservableList"/>
<import type="com.example.my.app.Fields"/>
<variable name="user" type="ObservableList<Object>"/>
</data>
这一段。照着你写的,我也像你这样引进了
<import type="android.databinding.ObservableList"/>
<variable name="user" type="ObservableList<Object>"/>
但是 type="ObservableList<Object>"会提示报错,然后我把<Object>去掉是可以的,到底应该是什么
<ListView
android:id="@+id/tasks_list"
app:items="@{viewmodel.items}"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
//在viewmodel中声明
public final ObservableList<Object> items = new ObservableArrayList<>();