标签:
2015年的Google IO大会上,Android 团队发布了一个数据绑定框架(Data Binding Library),官方原生支持 MVVM 模型。以后可以直接在 layout 布局 xml 文件中绑定数据了,无需再 findViewById 然后手工设置数据了。其语法和使用方式和 JSP 中的 EL 表达式非常类似。
Data Binding Library 是一个 support 库,支持 Android 2.1+ 版本 (API level 7+)。 由于该框架需要使用编译器来生成很多代码,所以需要配合新版本的 Android Studio (1.3.0-beta1 + 版本)才能使用,Gradle 插件1.5.0-alpha1以上。
例:
<?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>
Android Studio版本是1.3+,更新Support repository到最新的版本。
新建一个project,在dependencies中添加以下依赖
classpath "com.android.databinding:dataBinder:1.0-rc1"
新建module,并且在module的build.gradle文件中添加
apply plugin: ‘com.android.application‘
apply plugin: ‘com.android.databinding‘
Studio1.5+忽略以上两点,只需在对应module的build.gradle中添加
android {
....
dataBinding {
enabled = true
}
}
ps:即使依赖的库中使用了data binding,该module也必须在build.gradle中配置
<?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>
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;
}
}
@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);
}
Alternatively, you can get the view via:
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
在 ListView 或者 RecyclerView 的Adapter中使用:
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
例如,如果你的数据对象中有两个方法:
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>
<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}"/>
像java一样,java.lang.*
自动导入,可以直接使用
当存在同名的类时,其中一个可以使用别名
<import type="android.view.View"/>
<import type="com.example.real.estate.View" alias="Vista"/>
导入类还可以用于在表达式中引用静态属性和方法
<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"/>
自动生成的binding类会为每个声明的变量生成setter和getter方法,在调用setter方法之前变量的值为java默认值。
当表达式中需要使用时,会自动生成一个名为“context”的变量,“context”的值是由根布局的getContext()获得,如果手动声明了一个名为“context”的变量,默认的会被覆盖。
默认Binding类会根据布局文件名自动生成,放在module包下的databinding包中,如布局文件contact_item.xml
会自动生成ContactItemBinding,如果module包名是com.example.my.app
,这个类将被放在com.example.my.app.databinding
下。通过class属性,可以修改Binding类的类名和所在包。
databinding包下,类名为ContactItem
<data class="ContactItem">
...
</data>
module包下
<data class=".ContactItem">
...
</data>
指定包名
<data class="com.example.ContactItem">
...
</data>
使用include时,需要将变量传递到被包含的布局中,此时name.xml
和contact.xml
中必须有user变量。
<?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不支持<merge>
下直接的子元素使用include,比如下面的就不支持:
<?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>
以下表达式和java的一样
* 算术表达式 + - / * %
* 字符串连接 +
* 逻辑运算符 && ||
* 位运算符 & | ^
* 一元运算符 + - ! ~
* 位移运算符 >> >>> <<
* 关系运算符 == > < >= <=
* instanceof
* 分组 ()
* 字面值 - 字符, 字符串, 数字, null
* 类型转换
* 方法调用
* 访问属性
* 访问数组 []
* 三元运算符 ?:
例:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName=‘@{"image_" + id}‘
java中可以使用而这里不能使用的
* this
* super
* new
* Explicit generic invocation
当表达式左边不为null时使用左边的值,如果为null使用右边的
android:text="@{user.displayName ?? user.lastName}"
等效于
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
生成的data binding代码会自动检查空值,避免空指针。例如表达式@{user.name}
,如果user
是null
,user.name
将使用默认值null
,user.age
将使用默认值0。
<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]}"
android:text=‘@{map["firstName"]}‘
android:text="@{map[`firstName`}"
android:text="@{map["firstName"]}"
直接在表达式中使用resources
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
Format strings and plurals may be evaluated by providing parameters:
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"
When a plural takes multiple parameters, all parameters should be passed:
Have an orange
Have %d oranges
android:text="@{@plurals/orange(orangeCount, orangeCount)}"
Some resources require explicit type evaluation.
Type | Normal Reference | Expression Reference |
---|---|---|
String[] | @array | @stringArray |
int[] | @array | @intArray |
TypedArray | @array | @typedArray |
Animator | @animator | @animator |
StateListAnimator | @animator | @stateListAnimator |
color int | @color | @color |
ColorStateList | @color | @colorStateList |
任何一个简单的Java对象(POJO)都可以用来绑定,但是修改一个POJO不能够触发UI更新。
以下是3种数据改变通知机制,Observable objects, observable fields, 和 observable collections,当这3种数据对象绑定到UI,并且数据改变时UI会自动更新
实现 android.databinding.Observable
接口。为了方便,Android提供了基类BaseObservable
,实现了监听器的注册机制。
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
中生成一个entry。 BR是在编译时生成在module的一个类,功能与R.java类似。
属性较少时可以使用ObservableField
,包含ObservableBoolean
, ObservableByte
, ObservableChar
, ObservableShort
, ObservableInt
, ObservableLong
, ObservableFloat
, ObservableDouble
, 和 ObservableParcelable
,简单的POJO就可以实现。
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();
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<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类引用了layout中的View,就如前面说的,Binding的类名和包名都可以定制,生成的binding类都继承自ViewDataBinding
。
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
If the layout was inflated using a different mechanism, it may be bound separately:
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId, parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
如果布局中某个View设置了id,则生成的binding类中会包含:
public final TextView firstName;
public final TextView lastName;
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
...>
<ViewStub
android:id="@+id/view_stub"
android:layout="@layout/view_stub"
... />
</LinearLayout>
</layout>
binding = DataBindingUtil.setContentView(this, R.layout.activity_view_stub);
binding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub stub, View inflated) {
ViewStubBinding binding = DataBindingUtil.bind(inflated);
User user = new User("fee", "lang");
binding.setUser(user);
}
});
以 RecyclerView 为例,Adapter 的 DataBinding 需要动态生成,因此我们可以在 onCreateViewHolder 的时候创建这个 DataBinding,然后在 onBindViewHolder 中获取这个 DataBinding。
public void onBindViewHolder(BindingHolder holder, int position) {
final T item = mItems.get(position);
holder.getBinding().setVariable(BR.item, item);
holder.getBinding().executePendingBindings();
}
当一个变量改变时,binding会在下一帧时改变UI,需要立刻执行,可以使用executePendingBindings()
方法。
当一个绑定的值改变时,可以指定调用哪个方法来设置值
没有配置自定义属性,data binding会自动找到对应的setAttribute
方法(命名空间无所谓),注意表达式的返回值,必要的时候进行类型转换。
<android.support.v4.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}"/>
如果不想调用根据名字匹配的setter,可以通过BindingMethods
注解重新匹配。
@BindingMethods({
@BindingMethod(type = "android.widget.ImageView",
attribute = "android:tint",
method = "setImageTintList"),
})
通常开发者不需要重命名setters
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
还可以接收多个参数
@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);
}
<ImageView app:imageUrl=“@{venue.imageUrl}”
app:error=“@{@drawable/venueError}”/>
这个适配器会在imageUrl和error都在ImageView中设置,imageUrl是字符串,error时drawable时调用
Binding adapter methods may optionally take the old values in their handlers. A method taking old and new values should have all old values for the attributes come first, followed by the new values:
@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);
}
}
}
当一个listener有多个方法时,必须分成多个listener。比如View.OnAttachStateChangeListener
有两个方法: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);
}
因为改变一个listener经常会影响另一个,所以我们需要有3个不同的绑定适配器。
@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);
}
}
}
The above example is slightly more complicated than normal because View uses add and remove for the listener instead of a set method for View.OnAttachStateChangeListener. The android.databinding.adapters.ListenerUtil class helps keep track of the previous listeners so that they may be removed in the Binding Adaper.
By annotating the interfaces OnViewDetachedFromWindow
and OnViewAttachedToWindow
with @TargetApi(VERSION_CODES.HONEYCOMB_MR1)
, the data binding code generator knows that the listener should only be generated when running on Honeycomb MR1 and new devices, the same version supported by addOnAttachStateChangeListener(View.OnAttachStateChangeListener).
当binding表达式返回一个对象时,会从automatic, renamed, 和 custom setters 中选择一个,这个对象会被转换成选择的setter的参数类型。
这方便了用ObservableMaps保存数据,例如:
<TextView
android:text=‘@{userMap["lastName"]}‘
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
userMap
返回了一个 Object ,这个 Object 将会自动转换成 setText(CharSequence)
的参数类型。当参数类型不明确时,开发者需要在表达式中进行转换。
有时特殊的类型需要自动转换。比如,当设置背景时:
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
这里,背景是一个 Drawable
,但是颜色是一个整数,需要把 int
转换成 ColorDrawable
,可以使用带有 BindingConversion
注解的一个静态方法:
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
Note:不要将上面的和以下混淆
<View
android:background="@{isError ? @drawable/error : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
标签:
原文地址:http://blog.csdn.net/qian1127/article/details/51191956