配置环境
data binding是Google推出的一个实现MVVM的一个支持库。可以极大的简化开发工作。它非常灵活,且支持从android2.1开始。
为了使用data binding,Android gradle plugin必须是1.5.0-alpha1或者以上版本, Android Studio 1.3或者以上。
在项目项目下的build.gradle添加代码:
android {
....
dataBinding {
enabled = true
}
}
1.Layout Files
data-binding的layout文件与普通layout有点轻微的不一样,多了layout的根节点。
//activity_main.xml
<?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="user" type="com.example.User"/>
声明变量
在布局中使用上面的变量。
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
User的下面两种定义是等效的。
//POJO
public class User {
public final String firstName;
public final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
//JavaBean
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;
}
}
2.绑定数据
比如上面我们创建了activity_main文件,就会自动生成绑定类MainActivityBinding,它会保持从layout属性(比如user变量)到 layout view的所有绑定。然后可以使用MainActivityBinding为绑定表达式设置值,如下代码:
@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);
}
下面两种是等效的:
MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
如果是在ListView或者RecyclerView的adapter里,使用数据绑定items,下面两种是等效的。
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
3.事件处理
Data Binding允许使用表达式来处理View派发的事件,比如:View.OnClick,有两种方式可以处理事件:方法引用和监听Binding,方法引用和监听Binding的主要区别是:真实的监听器会被创建,仅当数据被绑定的时,而不是监听器被触发。如果想要事件发生时,调用表达式,可以使用监听Binding.
3.1 方法引用
通过”::”引用方法,
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>
3.2 监听Bindings
Android Gradle Plugin 2.0及以后添加的特性。
例如:有一个Present类
public class Presenter {
public void onSaveClick(Task task){}
}
然后像下面一样,把click事件绑定给Present,如下:
<?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>
上面的代码可以发现,监听器是通过lambda表达式实现的。上面的lambda表达式是没有参数,我们也可以添加参数:
android:onClick="@{(view) -> presenter.onSaveClick(task)}"
这个参数也可以被使用如下:
public class Presenter {
public void onSaveClick(View view, Task task){}
}
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"
也可以使用多个参数:
public class Presenter {
public void onCompletedChanged(Task task, boolean completed){}
}
<CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"
android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />
假如监听事件有返回值,相应的监听Bindings也必须有返回值,如下:
public class Presenter {
public boolean onLongClick(View view, Task task){}
}
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"
也可以使用三元表达式,使用void作为符号。
<!--使用void作为符号,代表什么都不做-->
android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
避免使用复杂的监听器,尽量把实现细节放到回调的方法里。
4.Layout布局更多细节
4.1 imports
可以像Java一样import类
<data>
<import type="android.view.View"/>
</data>
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/> //使用View类
假如类名会产生冲突,可以使用alias定义别名,如下:
<import type="android.view.View"/>
<import type="com.example.real.estate.View"
alias="Vista"/>
如何在layout文件中定义一个List<User>
变量
<data>
<import type="com.example.User"/>
<import type="java.util.List"/>
<variable name="user" type="User"/>
<variable name="userList" type="List<User>"/>//<==<, >==>
</data>
Android Studio会为layout文件自动Import
<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"/>
注意:java.lang.*被自动import了
4.2 变量
变量类型是编译时检测的,所以要想变量实现Observable,那么它对应的类型必须要实现它。
<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>
4.3 自定义绑定类名
一般情况,layout文件对应的绑定类是自动生成的,比如contact_item.xml对应ContactItemBinding,如果module的包名是com.example.my.app,那么ContactItemBinding会默认放在com.example.my.app.databinding.
通过class属性来自定义绑定类名
//通过class属性定义
<data class="ContactItem">
...
</data>
通过”.”修改绑定类的包名
//直接放在module的包名下,比如com.example.my.app.ContactItem
<data class=".ContactItem">
...
</data>
也可以指定完整的包名
//那么绑定类的全路径就是com.example.ContactItem
<data class="com.example.ContactItem">
...
</data>
4.4 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>
name.xml和contacts.xml中必须有一个user变量
不支持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>
4.5表达式语言
4.5.1 有点像Java,如下支持:
- 数学表达式: + - / * %
- String连接符 +
- 逻辑运算符 && ||
- 二元运算符 & | ^
- 一元运算符 + - ! ~
- 位移 >> >>> <<
- 比较 == ,> ,< ,>= ,<=
- instanceof
- 分组 ()
- 字符- character, String, numeric, null
- 转换 Cast
- 方法调用
- 字段调用
- 数组访问 []
- 三元运算符 ?:
比如例子:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
4.5.2 在Java中支持,但是databinding不支持:
- this
- super
- new
- 显式泛型调用
4.5.3 “??”的功能,下面两断代码是等效的。
android:text="@{user.displayName ?? user.lastName}"
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
4.5.4 自动处理空指针异常
下面这段代码,如果user等于Null,那么user.lastName给使用默认分配的值(null).
android:text="@{user.lastName}"
4.5.5 使用集合
<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]}"
4.5.6 使用字符串文字,下面三种是等效的
android:text='@{map["firstName"]}'
android:text="@{map[`firstName`}"
android:text="@{map['firstName']}"
4.5.7 使用resources, 允许如下使用resources
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
如果是格式化strings和plurals
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"
一些resources需要显式调用,对应关系如下:
5. 数据对象
POJO可以在data binding中使用,但是修改对象不会导致UI刷新。有三种机制提供对象变化通知
- observable 对象
- observable 字段
- observable集合
当这三种对象发生变化,UI会自动刷新。
5.1 observable对象
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);
}
}
Bindable注释会在编译是自动生成BR类
5.2 observable字段
ObservableField,ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, ObservableParcelable
private static class User {
public final ObservableField<String> firstName =
new ObservableField<>();
public final ObservableField<String> lastName =
new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
使用方式:
user.firstName.set("Google");
int age = user.age.get();
5.3 observable集合
提供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"/>
6. 生成绑定
我们上面讨论了,绑定类的类名和包名是可以自定义的。所有的绑定类都是继承ViewDataBinding
6.1 创建
提供了如下几种方式:
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
有些时候,绑定类名不能提前预知(比如在做封装类时),那么可以用DataBindingUtil类创建绑定。
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
6.2 Views With IDs
布局文件:
<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}"
android:id="@+id/firstName"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"
android:id="@+id/lastName"/>
</LinearLayout>
</layout>
在绑定类中,有自动生成如下
public final TextView firstName;
public final TextView lastName;
6.3 变量
布局如下:
<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>
绑定类中,会自动生成如下代码:
public abstract com.example.User getUser();
public abstract void setUser(com.example.User user);
public abstract Drawable getImage();
public abstract void setImage(Drawable image);
public abstract String getNote();
public abstract void setNote(String note);
6.4 ViewStubs
通过ViewStubProxy来代理。
6.5 高级绑定
有些时候,我们不知道具体的绑定类,比如RecyclerView.Adapter填充任意的layout,
public void onBindViewHolder(BindingHolder holder, int position) {
final T item = mItems.get(position);
holder.getBinding().setVariable(BR.item, item);
holder.getBinding().executePendingBindings();//Immediate Binding
}
7. 属性setters
当绑定值发生变化时,生成的绑定类必须使用绑定表达式在View上调用setter方法。 data binding有方法来自定义调用哪个方法来设置值.
7.1 自动的setters
DrawerLayout没有属性,但是有setScrimColor
和setDrawerListerner
,可以使用自动的setters来完成方法调用
<android.support.v4.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}"/>
7.2 重命名setters
可以通过BindingMethods来实现重命名setters
@BindingMethods({
@BindingMethod(type = "android.widget.ImageView",
attribute = "android:tint",
method = "setImageTintList"),
})
然后android:tint调用的是setImageTintList(ColorStateList)
,而不是setTint
开发人员不太可能需要重命名设置器; android框架的属性已经被实现了。
7.3 自定义setters
比如没有android:paddingLeft,因为setPadding(left, top, right, bottom) 已经存在了,但是我们可以通过BindingAdapter来定义一个android:paddingLeft,如下代码
@BindingAdapter(“android:paddingLeft”)
public static void setPaddingLeft(View view, int padding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
BindingAdapter非常适用于加载图片,比如
<ImageView app:imageUrl="@{venue.imageUrl}"
app:error="@{@drawable/venueError}"/>
可以通过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);
}
BindingAdapters可以选择性的提供oldValue,oldValue必须放在第一个。如下
@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);
}
}
}
如果有多个方法,需要分解,比如View.OnAttachStateChangeListerner有两个方法:onViewAttachedToWindow()和onViewDetachedFromWindow(). 实现如下:
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewDetachedFromWindow {
void onViewDetachedFromWindow(View v);
}
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewAttachedToWindow {
void onViewAttachedToWindow(View v);
}
@BindingAdapter("android:onViewAttachedToWindow")
public static void setListener(View view, OnViewAttachedToWindow attached) {
setListener(view, null, attached);
}
@BindingAdapter("android:onViewDetachedFromWindow")
public static void setListener(View view, OnViewDetachedFromWindow detached) {
setListener(view, detached, null);
}
@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"})
public static void setListener(View view, final OnViewDetachedFromWindow detach,
final OnViewAttachedToWindow attach) {
if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) {
final OnAttachStateChangeListener newListener;
if (detach == null && attach == null) {
newListener = null;
} else {
newListener = new OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
if (attach != null) {
attach.onViewAttachedToWindow(v);
}
}
@Override
public void onViewDetachedFromWindow(View v) {
if (detach != null) {
detach.onViewDetachedFromWindow(v);
}
}
};
}
final OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view,
newListener, R.id.onAttachStateChangeListener);
if (oldListener != null) {
view.removeOnAttachStateChangeListener(oldListener);
}
if (newListener != null) {
view.addOnAttachStateChangeListener(newListener);
}
}
}
8.转换器
8.1 对象转换
<TextView
android:text='@{userMap["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
userMap返回一个对象,这个对象类型会自动转换成TextView.setText(CharSequence)指定的CharSequence,当类型无法自动转换时,开发者性需要显式转换。
8.2 自定义转换
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
background接受Drawable,但是color是int,所以使用到了@BindingConversion来实现自定义转换
@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"/>
9.AS对databinding的支持
支持如下:
1. 语法高亮
2. 表达式语法错误提示
3. 代码自动完成
4. 引用使用navigation查找和定位,支持文档。
预览区域支持default值
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName, default=PLACEHOLDER}"/>
怎么使用默认值:参考http://tools.android.com/tips/layout-designtime-attributes
DataBinding例子:Google的https://github.com/google/android-ui-toolkit-demos