任何不含业务逻辑的java简单对象(POJO)可用于数据绑定,但修改POJO不能使UI更新。而通过数据绑定可以使数据对象感知到数据的变化。有三种不同的感知数据改变的机制,可见对象,可见字段,和可见集合。
当一个可见数据对象绑定到用户界面和数据对象变化的属性时,用户界面将自动更新。
一个实现可见接口的类,允许把监听器和对象绑定,以便监听该对象的所有属性的变化。
可见接口有添加和删除侦听器的机制,但数据改变的通知机制取决于开发者。为了使开发更简洁,创建一个基类(BaseObservable)来实现监听器注册机制。当属性改变时,数据实现类仍负责告知机制,其中getter方法加@Bindable注释,并在setter方法中告知属性的变化。
01.
private
static
class
User
extends
BaseObservable {
02.
private
String firstName;
03.
private
String lastName;
04.
@Bindable
05.
public
String getFirstName() {
06.
return
this
.firstName;
07.
}
08.
@Bindable
09.
public
String getLastName() {
10.
return
this
.lastName;
11.
}
12.
public
void
setFirstName(String firstName) {
13.
this
.firstName = firstName;
14.
notifyPropertyChanged(BR.firstName);
15.
}
16.
public
void
setLastName(String lastName) {
17.
this
.lastName = lastName;
18.
notifyPropertyChanged(BR.lastName);
19.
}
20.
}
创建可见类的过程中,开发人员想节省时间或有很多属性时可以使用可见字段,例如一些常用的可见类型字段:ObservableBoolean, ObservableByte, ObservableChar,ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, andObservableParcelable。
可见字段是有独立字段的可见对象。原始版本在访问操作中避免装箱和拆箱操作。为方便使用,在数据类创建使用public final修饰的字段。
1.
private
static
class
User {
2.
public
final
ObservableField firstName =
3.
new
ObservableField<>();
4.
public
final
ObservableField lastName =
5.
new
ObservableField<>();
6.
public
final
ObservableInt age =
new
ObservableInt();
7.
}
1.
user.firstName.set(
"Google"
);
2.
int
age = user.age.get();
一些应用程序使用更多的动态结构来保存数据。可见集合支持键控存取方式访问这些数据对象。当键是一个像字符串这样的引用类型时,可使用ObservableArrayMap。
1.
ObservableArrayMap user =
new
ObservableArrayMap<>();
2.
user.put(
"firstName"
,
"Google"
);
3.
user.put(
"lastName"
,
"Inc."
);
4.
user.put(
"age"
,
17
);
01.
02.
<
import
type=
"android.databinding.ObservableMap"
/>
03.
"user"
type=
"ObservableMap"
/>
04.
05.
…
06.
07.
android:text=
'@{user["lastName"]}'
08.
android:layout_width=
"wrap_content"
09.
android:layout_height=
"wrap_content"
/>
10.
11.
android:text=
'@{String.valueOf(1 + (Integer)user["age"])}'
12.
android:layout_width=
"wrap_content"
13.
android:layout_height=
"wrap_content"
/>
1.
ObservableArrayList
new
ObservableArrayList<>();
2.
user.add(
"Google"
);
3.
user.add(
"Inc."
);
4.
user.add(
17
);
01.
02.
<
import
type=
"android.databinding.ObservableList"
/>
03.
<
import
type=
"com.example.my.app.Fields"
/>
04.
"user"
type=
"ObservableList
/>
05.
06.
…
07.
08.
android:text=
'@{user[Fields.LAST_NAME]}'
09.
android:layout_width=
"wrap_content"
10.
android:layout_height=
"wrap_content"
/>
11.
12.
android:text=
'@{String.valueOf(1 + (Integer)user[Fields.AGE])}'
13.
android:layout_width=
"wrap_content"
14.
android:layout_height=
"wrap_content"
/>
任何不含业务逻辑的java简单对象(POJO)可用于数据绑定,但修改POJO不能使UI更新。而通过数据绑定可以使数据对象感知到数据的变化。有三种不同的感知数据改变的机制,可见对象,可见字段,和可见集合。
当一个可见数据对象绑定到用户界面和数据对象变化的属性时,用户界面将自动更新。
生成绑定类链接在布局视图的布局变量,正如前面所讨论的,绑定的名称和包可以自定义。生成的绑定类都继承ViewDataBinding。
应该立即创建绑定,以确保布局中的表达式与视图的绑定不干扰视图层。有几种绑定到布局的方法。最常见的是在绑定类调用静态方法inflate。inflate解析视图,并完成数据绑定。还有一个更简单的版本,只需要在inflate方法中引入LayoutInflater或再加上ViewGroup:
1.
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
2.
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup,
false
);
如果布局inflate机制有变化,也可以使用分开绑定的机制。
1.
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
1.
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
2.
parent, attachToParent);
3.
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
在布局中每个有ID的视图将相应生成一个public final字段的变量,绑定类只调用一次布局,并创建每个视图。这种机制比调用findViewById方法更有效率。
01.
"http://schemas.android.com/apk/res/android"
>
02.
03.
"user"
type=
"com.example.User"
/>
04.
05.
06.
android:orientation=
"vertical"
07.
android:layout_width=
"match_parent"
08.
android:layout_height=
"match_parent"
>
09.
"wrap_content"
10.
android:layout_height=
"wrap_content"
11.
android:text=
"@{user.firstName}"
12.
android:id=
"@+id/firstName"
/>
13.
"wrap_content"
14.
android:layout_height=
"wrap_content"
15.
android:text=
"@{user.lastName}"
16.
android:id=
"@+id/lastName"
/>
17.
18.
1.
public
final
TextView firstName;
2.
public
final
TextView lastName;
每个变量会给出存取方法:
1.
2.
<
import
type=
"android.graphics.drawable.Drawable"
/>
3.
"user"
type=
"com.example.User"
/>
4.
"image"
type=
"Drawable"
/>
5.
"note"
type=
"String"
/>
6.
1.
public
abstract
com.example.User getUser();
2.
public
abstract
void
setUser(com.example.User user);
3.
public
abstract
Drawable getImage();
4.
public
abstract
void
setImage(Drawable image);
5.
public
abstract
String getNote();
6.
public
abstract
void
setNote(String note);
viewstubs与普通视图不同。他们开始是是一个不可视并且大小为0的视图,可以延迟到运行时填充布局资源。设置为Visible或调用inflate()之后,就会填充布局资源,ViewStub便会被填充的视图替代。
由于ViewStub会在视图层消失,为了正常的连接,对应的绑定对象也要随之消失。由于视图层是final类型的,ViewStubProxy对象替代ViewStub 后,开发者可以访问 ViewStub,并且当 ViewStub 在视图层中被加载时,开发者也可以访问加载的视图。
当解析其他布局时,新的布局中要建立绑定关系。因此,ViewStubProxy 对象要监听ViewStub的OnInflateListener并建立绑定。开发者可以在ViewStubProxy 对象上建立一个OnInflateListener,绑定建立后,便可调用OnInflateListener。
有时具体的绑定类会不被识别。例如,操作任意布局的RecyclerView.Adapter必须在onBindViewHolder中指定绑定值,才可识别出对应的绑定类。
在这个例子中,RecyclerView绑定的所有布局都有一个item变量。BindingHolder 对象通过引用getBinding() 方法获取 ViewDataBinding 基类。
1.
public
void
onBindViewHolder(BindingHolder holder,
int
position) {
2.
final
T item = mItems.get(position);
3.
holder.getBinding().setVariable(BR.item, item);
4.
holder.getBinding().executePendingBindings();
5.
}
当变量变化时,绑定在下一帧到来前也要随之改变。然而,当绑定需要立即执行时,可以调用强制执行方法executePendingBindings()。
可以在后台线程中改变数据模型,只要它不是集合。数据绑定将每个变量或字段保存到本地,以避免任何并发问题。
只要绑定值有变化,生成的绑定类就会在视图中调用setter方法。数据绑定框架可以自定义要调用的方法来设置值。
对一个属性来说,数据绑定试图查找setAttribute方法。这与属性的命名空间没有关系,只与属性本身有关。
例如 TextView 的属性 android:text 上的表达式,数据绑定将查找setText(String) 方法,如果表达式返回值为 int,则会调用 setText(int)方法。注意表达式要返回正确的类型,有必要则使用cast进行类型转换。即使没有给定的属性,数据绑定也会执行。可以通过数据绑定调用setter方法创建属性。例如,DrawerLayout没有任何属性,但有很多setter方法,可以调用setter方法自动创建属性。
1.
2.
android:layout_width=
"wrap_content"
3.
android:layout_height=
"wrap_content"
4.
app:scrimColor=
"@{@color/scrim}"
5.
app:drawerListener=
"@{fragment.drawerListener}"
/>
对于属性setter方法与名字不匹配的情况,可以通过BindingMethods注释关联名字和方法。类中包含BindingMethod注释,其中可以重命名set方法。例如,android:tint 属性与setImageTintList(ColorStateList)方法相关,而与setTint不对应。
1.
@BindingMethods
({
2.
@BindingMethod
(type =
"android.widget.ImageView"
,
3.
attribute =
"android:tint"
,
4.
method =
"setImageTintList"
),
5.
})
一些属性需要自定义绑定逻辑,android:paddingLeft 属性并没有对应的setter方法,但是存在setPadding(left, top, right, bottom)方法。通过 BindingAdapter 注释来自定义属性调用的静态setter方法。android 系统已经创建了 BindingAdapter 函数,下面是 paddingLeft 属性对应的函数:
1.
@BindingAdapter
(
"android:paddingLeft"
)
2.
public
static
void
setPaddingLeft(View view,
int
padding) {
3.
view.setPadding(padding,
4.
view.getPaddingTop(),
5.
view.getPaddingRight(),
6.
view.getPaddingBottom());
7.
}
1.
@BindingAdapter
({
"bind:imageUrl"
,
"bind:error"
})
2.
public
static
void
loadImage(ImageView view, String url, Drawable error) {
3.
Picasso.with(view.getContext()).load(url).error(error).into(view);
4.
}
5.
6.
app:error=“@{
@drawable
/venueError}”/>
在匹配适配器的时候, 会忽略自定义的命名空间你也可以为 android 命名空间的属性自定义适配器。
01.
@BindingAdapter
(
"android:paddingLeft"
)
02.
public
static
void
setPaddingLeft(View view,
int
oldPadding,
int
newPadding) {
03.
if
(oldPadding != newPadding) {
04.
view.setPadding(newPadding,
05.
view.getPaddingTop(),
06.
view.getPaddingRight(),
07.
view.getPaddingBottom());
08.
}
09.
}
01.
@BindingAdapter
(
"android:onLayoutChange"
)
02.
public
static
void
setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue,
03.
View.OnLayoutChangeListener newValue) {
04.
if
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
05.
if
(oldValue !=
null
) {
06.
view.removeOnLayoutChangeListener(oldValue);
07.
}
08.
if
(newValue !=
null
) {
09.
view.addOnLayoutChangeListener(newValue);
10.
}
11.
}
12.
}
例如,OnAttachStateChangeListener包含两个方法, onViewAttachedToWindow() 和onViewDetachedFromWindow(),必须创建两个接口用于区分属性和处理程序。
01.
@TargetApi
(VERSION_CODES.HONEYCOMB_MR1)
02.
public
interface
OnViewDetachedFromWindow {
03.
void
onViewDetachedFromWindow(View v);
04.
}
05.
06.
@TargetApi
(VERSION_CODES.HONEYCOMB_MR1)
07.
public
interface
OnViewAttachedToWindow {
08.
void
onViewAttachedToWindow(View v);
09.
}
01.
@BindingAdapter
(
"android:onViewAttachedToWindow"
)
02.
public
static
void
setListener(View view, OnViewAttachedToWindow attached) {
03.
setListener(view,
null
, attached);
04.
}
05.
@BindingAdapter
(
"android:onViewDetachedFromWindow"
)
06.
public
static
void
setListener(View view, OnViewDetachedFromWindow detached) {
07.
setListener(view, detached,
null
);
08.
}
09.
@BindingAdapter
({
"android:onViewDetachedFromWindow"
,
"android:onViewAttachedToWindow"
})
10.
public
static
void
setListener(View view,
final
OnViewDetachedFromWindow detach,
11.
final
OnViewAttachedToWindow attach) {
12.
if
(VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) {
13.
final
OnAttachStateChangeListener newListener;
14.
if
(detach ==
null
&& attach ==
null
) {
15.
newListener =
null
;
16.
}
else
{
17.
newListener =
new
OnAttachStateChangeListener() {
18.
@Override
19.
public
void
onViewAttachedToWindow(View v) {
20.
if
(attach !=
null
) {
21.
attach.onViewAttachedToWindow(v);
22.
}
23.
}
24.
@Override
25.
public
void
onViewDetachedFromWindow(View v) {
26.
if
(detach !=
null
) {
27.
detach.onViewDetachedFromWindow(v);
28.
}
29.
}
30.
};
31.
}
32.
final
OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view,
33.
newListener, R.id.onAttachStateChangeListener);
34.
if
(oldListener !=
null
) {
35.
view.removeOnAttachStateChangeListener(oldListener);
36.
}
37.
if
(newListener !=
null
) {
38.
view.addOnAttachStateChangeListener(newListener);
39.
}
40.
}
41.
}
android.databinding.adapters.ListenerUtil类帮助跟踪之前的监听器,以便在绑定适配器中及时移除相应的监听器。
通过用@TargetApi(VERSION_CODES.HONEYCOMB_MR1)注释接口OnViewDetachedFromWindow and OnViewAttachedToWindow 。数据绑定代码生成器知道只能在产生蜂窝MR1和新设备中运行时才会生成监听器。
当绑定表达式返回一个对象时候,将会自动调用 set 函数、重命名的函数、或者自定义的 setter 中的一个。表达式返回的对象将会转换为该函数的参数类型。
使用 ObservableMaps 来保存数据会比较简单。例如:
1.
2.
android:text=
'@{userMap["lastName"]}'
3.
android:layout_width=
"wrap_content"
4.
android:layout_height=
"wrap_content"
/>
有时候参数应该可以自动转换,例如
1.
2.
android:background=
"@{isError ? @color/red : @color/white}"
3.
android:layout_width=
"wrap_content"
4.
android:layout_height=
"wrap_content"
/>
1.
@BindingConversion
2.
public
static
ColorDrawable convertColorToDrawable(
int
color) {
3.
return
new
ColorDrawable(color);
4.
}
1.
2.
android:background=
"@{isError ? @drawable/error : @color/white}"
3.
android:layout_width=
"wrap_content"
4.
android:layout_height=
"wrap_content"
/>
Android Studio支持数据绑定表达式的语法高亮显示,并在编辑器中显示任何表达式语言语法错误。
预览窗格显示数据绑定表达式的默认值。以下从布局XML文件的元素实例摘录。
1.
"wrap_content"
2.
android:layout_height=
"wrap_content"
3.
android:text=
"@{user.firstName, default=PLACEHOLDER}"
/>