以下是Data Binding Library译文,原文链接
DataBinding库
这篇文章解释了通过DataBinding库如何进行布局编写以及在做到绑定逻辑和布局的同时做到最小化耦合.
DataBinding库非常灵活并且广泛兼容-它是一个辅助库,所以你可以在所有Android平台上使用它,最小到Android2.1(API等级7+)
为了使用DataBinding库,Android Gradle插件版本要求1.5.0-alpha1以上,升级插件详细步骤请看Android Gradle插件升级
构建环境
进行数据绑定的第一步,下载辅助库.
为了让你的应用能够使用数据绑定,在模块的build.gradle文件里面添加dataBinding元素,代码片段如下:
android {
...
dataBinding {
enabled true
}
}
同时确定你使用了兼容版本的AndroidStudio,1.3及以上支持数据绑定参考Andoid Studio Support for Data Binding
参考原文写的一些例子
- MainActivity 数据绑定
- AttributesExampleActivity 属性设置
- CustomBindingActivity 自定义绑定名和包名
- ObservableExampleActivity 数据变化监控并更新UI
- DynamicVariablesExampleActivity 结合ReyclerView
数据绑定布局文件
首先让我们写下第一个数据绑定表达式集
数据绑定布局文件和普通布局文件有一点不同,它以layout作为根标记,然后是data和一个view根元素作为子元素.View根元素是当你不使用data binding的时候的布局的根元素,以下是一个例子:
以上,在data里面的variable声明了一个可能会在布局里面用到的属性值.
布局中用来给View的某个属性值赋值的表达式使用了"@{}"语法,在上面的例子里,TextView的文本设置为user的firstName,如下:
数据对象
让我们假设现有你有一个User类的POJO对象(Plain Ordinary Java Object,注: 不含业务逻辑的java简单对象)
public class User {
public final String firstName;
public final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
这种对象的数据成员永远改变.在应用程序中,数据在被读取一次之后从不改变是非常常见的.也可能使用一个JavaBeans对象:
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;
}
}
从数据角度来看,这两个类是同等的.TextView中的android:text属性用到@{user.firstName}对于前面一个类而言会访问它的firstName成员,对后一个类而言会使用它的getFirstName()方法.另外,该表达也可以被解析为firstName()如果该方法存在的话.如:
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 firstName() {
return this.firstName;
}
public String getLastName() {
return this.lastName;
}
}
优先级:getFirstName() > firstName() > firstName
绑定数据
默认将提取布局文件名并添加Binding后缀按照Pascal命名规则自动生成一个binding类,以上的布局文件名是main_activity.xml,所以会产生一个名字为MainActivityBinding的类.这个类持有布局属性(例如:user变量)和布局中的视图生成的所有bindings,并且知道如何为绑定表达式赋值.创建绑定最简单的方式是在inflate时进行:
@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);
}
就是这么高效,运行应用你就可以在界面中看到Test User,另外你可以得到视图通过:
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
如果你在ListView或者RecyclerView的适配器中使用多个数据绑定,你可能会更喜欢以下方式(可以参考我例子中的DynamicVariablesExampleActivity):
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
事件处理
Data binding允许你写表达式用来处理从视图派发来的事件(例如:onClick),除了少数例外,事件属性名字由监听器方法控制.例如,View.OnLongClickListener有一个方法onLongClick(),所以对于长按事件的属性名字是android:onLongClick.有以下两种方式可以处理事件:
-
方法引用:
在你的表达式中,你可以引用符合监听器方法规则的方法.当对表达式的值是一个方法引用时,数据绑定把方法引用和所有者对象包装进一个监听器,然后把该监听器设置到目标视图.如果表达式的值为null,数据绑定不会去创建监听器同时设置一个null监听器到目标视图.
-
监听器绑定:
这是一个lambda表达式,在事件发生的时候它的值被计算.Data binding总是创建一个监听器然后设置到目标视图.当事件分发的时候,监听器计算lambda表达式的值.
方法引用
事件可以被直接绑定到处理器方法,类似android:onClick可以被指定为Activity的一个方法.相对于View#onClick属性该方法的一个主要优势在于该表达式是在编译时候进行处理的,如果该方法不存在或者它的识别标志不正确,你会收到一个编译错误.
方法引用和监听器绑定的主要区别在于它的监听器实现是在数据被绑定的时候被创建的而不是事件触发的时候.如果你更喜欢在事件发生时进行表达式计算,你应该使用监听器绑定.
为了把事件派发给它的处理器,使用将要被调用的方法构构建一个正常的绑定表达式.例如,如果你的数据对象有以下方法:
public class MyHandlers {
/*名字随意,参数类型匹配就行*/
public void onClickFriend(View view) { ... }
}
以下表达式可以为视图指定一个点击监听器
注意:表达式中的方法必须准确匹配来自Listener对象中的方法(也就是说MyHandlers中的onClickFriend方法的返回值和参数必须和View.OnClickListener的onClick方法一致)
监听器绑定
监听器绑定是在事件发生的时候进行表达式绑定.它和方法引用是类似的,但是它让你能够运行任意的绑定表达式.该功能只适用2.0及以上的Android Gradle插件.
在方法引用中,方法的参数必须和事件监听器的对应方法完全匹配.但是在监听器绑定中,只要返回值和监听器对应方法的返回值一致.例如,你又可以新建一个Presenter有下面的方法:
public class Presenter {
public void onSaveClick(Task task){}
}
然后你可以通过下面的方式绑定点击事件到该Presenter:
lambda表达式代表的监听器只允许作为表达式的根元素.当表达式中有callback被使用,数据绑定自动创建必要的监听器然后注册给事件.当事件触发时,data binding计算该表达式.在常规的绑定表达式中,在监听器表达式正在被计算时你仍然得到null且线程安全的数据绑定(直译).
在上面的例子中,我们没有定义传递给onClick(android.view.View)的view参数.监听器绑定为监听器参数提供了两个选择,你既可以忽略所有的参数,也可以都加上.例如:以上的表达式也可以这么写:
//实际上这里虽然给出了view参数,但是表达式没有用上它
android:onClick="@{(view) -> presenter.onSaveClick(task)}"
或者如果你想用的话,按照下面这种方式就行:
public class Presenter {
public void onSaveClick(View view, Task task){}
}
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"
你还可以使用lambda表达式添加更多的参数
public class Presenter {
public void onCompletedChanged(Task task, boolean completed){}
}
错误案例:
public class Presenter {
public void onCompletedChanged(Task task, boolean completed){}
}
如果你正在监听的事件存在非空的返回值,你的表达式也需要返回同样的返回值.例如,如果你要监听长按事件,你表达式需要返回boolean类型:
public class Presenter {
public boolean onLongClick(View view, Task task){}
}
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"
如果表达式因为空对象不能被计算.数据绑定根据类型返回对应java值,例如:引用型返回null,int返回0,boolean返回false,等等.
如果你需要使用一个有判断的表达式(例如:三元表达式),你可以使用void作为一个符号:
android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
避免复杂的监听器
监听器表达式功能强大而且可以能够使你的代码可读性增强.另外一方面会导致布局难读不好维护.应该尽量让表达式简单,只是传递来自UI的数据到到你的回调方法.你应该在表达式中调用的回调方法中处理任何业务逻辑.存在一些特殊的点击事件处理程序,它们需要android:onClick之外的属性来避免冲突.为了解决冲突,创建下面这些属性:
Class | Listener Setter | Attribute |
---|---|---|
SearchView | setOnSearchClickListener(View.OnClickListener) | android:onSearchClick |
ZoomControls | setOnZoomInClickListener(View.OnClickListener) | android:onZoomIn |
ZoomControls | setOnZoomOutClickListener(View.OnClickListener) | android:onZoomOut |
例如,给SearchView的点击事件设置监听器:
android:onSearchClick="@{handlers::onButtonClick}"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
Layout Details
Imports
在data元素里面,0个或者多个import元素可以被使用.这能像在java里面一样使用各种类.
现在你可以在你的绑定表达式中使用View类了.
如果存在某些类的名字存在冲突可以使用alias进行别名设置:
现在Vista可以用来引用com.example.real.estate.View,然后View可以用来引用android.view.View.被import的类型可以在变量定义或者表达式中被引用.
注意:Android Studio还不支持变量的自动导入.可以通过使用完全限定名来通过编译和解决问题.
在引用类成员或者类方法的时候,被导入的类型可能被用到
…
和Java一样,java.lang.*会被自动导入.
变量(variable)
在data元素里面可以使用任何数量的变量,每个变量用来描述一个属性值,该属性值可能在布局中的绑定表达式中被用到.
变量类型在编译的时候被检查,所以一个变量实现了Observable或者observable collection,应该在它的类型中得到反映.如果变量是一个没有实现Observable*接口的基类或者一个接口,那么该变量将不会被观察.
当针对不同的配置(分辨率,横竖屏,等)存在不同的布局文件,变量将会被合并.各个布局中的变量不应该存在冲突.
产生的Binding类将会有针对每个变量的setter和getter方法.这些变量会有默认的java值直到setter被调用-引用型默认为null,int型默认为0,boolean型默认为false,等等.
一个名字为context的特殊变量将会被生成以备表达式中引用.该context的值来自根布局的getContext方法,当然该context可能被直接声明的context给覆盖,如下:
自定义Binding类名
默认地,Binding类名是通过提取布局文件的名字,然后把各个字段的首字母大写并去除下划线,最后添加Binding得到的.它会在模块的包下面.例如,contact_item.xml将产生ContactItemBinding.如果模块的包名是com.example.my.app.它的全包名路径会是:com.example.my.app.databinding.
可以通过调整data元素的class属性来修改Binding的包名和类型,例如:
...
如果想用模块的包名和自定义类名可以用,以.作为前缀
...
在上面的例子里,ContactItem类会在模块包里面被产生,当然你也可以自定义包名:
...
includes
可以通过应用命令空间和变量名把父布局的变量传递给被包含进来的子布局:
在这里name.xml和contact.xml中必须包含一个user变量,如name.xml:
数据绑定不支持通过导入子布局作为merge元素的直接儿子,如下:
表达式语言
通用功能
这些表达式语言跟在java表达式里面看起来非常像,以下是相同的:
- 算术符 + - / * %
- 字符串连接符 +
- 逻辑运算符 && ||
- 位运算符 & | ^
- 一元运算符 + - ! ~
- 位移运算符 >> >>> <<
- 比较符 == > < >= <=
- instanceof
- Grouping ()
- 常量 - character, String, numeric, null
- Cast
- Method calls
- Field access
- Array access []
- 三元运算符 ?:
例子:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
不存在的运算符
相对于Java少数的运算符在表达式中不可用
- this
- super
- new
- 直接的方法调用.
空值合并符
空值合并符(??)会在做操作数不为空的时候会选择左操作数,否则右操作数
android:text="@{user.displayName ?? user.lastName}"
这个功能等同于下面的表达式
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
属性引用
正如之前谈到的,简易格式java引用.当一个表达式引用了类上边的一个属性值,对fields,getters和ObservableFields将使用同一种格式:
android:text="@{user.lastName}"
规避空指针异常
产生的数据绑定代码会自动去检查所有的空值避免空指针异常,例如:在@{user.name}表达式中,如果user为空的话,user.name将被指定一个默认值(null).如果你在引用user.age,因为age是一个整型,它的值为0.
集合(Collections)
常见集合: arrays, lists, sparse lists,和maps,可以很方便的通过[]操作符来访问.
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"
String常量
当你在属性值外边使用单引号的话,那么很简单地在表达式中使用双引号就行:
android:text='@{map["firstName"]}
也可以在属性值外边使用双引号,然后在表达式中的字符串常量应该使用单引号(')或者反引号(`)
经过试验: 单引号(')是不行的:
android:text="@{map[`firstName`}"
// 用单引号报错
android:text="@{map['firstName']}"
资源
通过正常语法访问资源可以作为表达式的一部分:
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
通过参数可以得到格式化字符串和复数:
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"
当复数带有多个参数时,应该传递所有的参数,如下:
Have an orange
Have %d oranges
// 第一个参数orangeCount用来决定是取单数还是复数形式的字符串(这里是1取Have an orange,其他取Have %d oranges)
// 第二个参数当取复数形式的字符串时,用来替换%d的.
// 参考 getQuantityString 实现
android:text="@{@plurals/orange(orangeCount, orangeCount)}"
注意:如果手机的语言环境设置为中文的话是没有复数形式的.
在表达式中有些类型要求显示类型指定(譬如: 只能通过stringArray访问字符串数组,不能通过array访问):
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 |
数据对象
任何POJO能被用来进行数据绑定,但是POJO的修改不会触发UI的更新.数据绑定真正厉害的地方在于拥有发出数据变化通知的能力.有多个不同的数据变化通知机制:Observable objects,observable fields和observable collections.
当它们中的某一个被绑定到UI,一旦数据对象的属性发生变化.UI会自动更新.
Observable Objects
如果某个类实现了Observable接口,允许binding在被绑定的对象上边附加一个监听器来监听对象的属性值变化.
Observable接口有增加和移除监听器的机制,但是通知取决于开发者.为了使开发简单化,BaseObservable基类被创建来实现监听器注册机制.数据类实现负责在属性变化时发出通知.通过在getter上边添加Bindable以及在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);
}
}
编译的时候Bindable注释将会在BR类中产生一个条目.BR类将会产生在模块报下面.如果这个基类不能被改变,Observable接口可能通过使用PropertyChangeRegistry来存储和通知监听器.
ObservableFields
在创建Observable 类时其实还是有点工作的,所以如果开发者想节省时间或者更少的属性,那么可以选择ObservableField和它的兄弟姐妹们 ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, 以及 ObservableParcelable.ObservableFields是独立的observable对象,它只有一个成员.原始版本避免在访问操作的时候进行打包和解包.通过创建一个公共的不可修改的成员来使用它:
private static class User {
public final ObservableField firstName =
new ObservableField<>();
public final ObservableField lastName =
new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
通过set和get方法来访问它:
user.firstName.set("Google");
int age = user.age.get();
Observable Collections
一些应用使用更多动态的数据结构来保存数据.Observable Collections允许通过键值来访问这些数据对象.
当键值是一个引用型时,如: String, ObservableArrayMap非常有用:
ObservableArrayMap user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
在布局中,可以通过String键来访问以上映射:
…
当键值是一个整型时,ObservableArrayList非常有用:
ObservableArrayList
在布局中,该列表可以通过索引访问:
…
Generated Binding
这个产生的绑定类把布局中的变量和视图连接起来.在前面我们说过,这个绑定类的包名和类型都可以自定义的.它是ViewDataBinding的子类.
Creating
为了确保在通过表达式绑定到视图之前,视图层次结构不受到干扰,这个绑定类应该在inflation之后尽快被创建.有几种方法可以绑定到布局,通常是使用绑定类的静态方法.这个inflate方法inflate视图层以及绑定一步到位.
还有更简单的版本只要 LayoutInflater作为参数,另外一个加一个ViewGroup:
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
如果布局是使用另外的机制进行inflate,那么它可以被分开绑定:
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
有时候绑定不能被提前进行.在这种情况下,绑定可以通过DataBindingUtil来创建:
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
Views With IDs
会为布局中拥有id的视图生成一个公共的不可修改成员.绑定在视图层次上面进行了一次传递,用来提取拥有ID的视图.这个机制比findViewById更加有效.例如:
将产生一个绑定类有下面的成员:
public final TextView firstName;
public final TextView lastName;
在有数据绑定的时候,ID显得不那么重要,但是免不了有些场合需要用到.
Variables
会产生每个变量对应的访问器方法:
在Binding中会产生setters和getters:
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);
ViewStubs
ViewStubs和正常的View有点不同.一开始它是不可见的,只是在被设置成可见的或者显式被inflate的时候,它将使用另外的布局替换掉自身.
因为ViewStub在视图层次中基本消失了,所以在Binding类中的View也要允许消失然后被回收.因为Views是不可更改的,ViewStubProxy用来替换掉 ViewStub,用来给开发者访问ViewStub和inflated的视图层次当ViewStub已经被inflate.
当inflate另外的布局,新的布局的binding被成生成.因此ViewStubProxy必须监听ViewStub的ViewStub.OnInflateListener,然后及时的生成Binding.因为只有一个能存在,ViewStubProxy允许开发者在建立绑定之后在ViewStub上面添加OnInflateListener.
Advanced Binding
Dynamic Variables
有时候,指定的绑定类是未知的.例如:RecyclerView.Adapter针对任意布局进行操作,不知道指定的绑定类.它仍然需要在onBindViewHolder(VH, int)里面指定绑定类.
在这个例子里,RecyclerView绑定的所有布局有一个item变量.BindingHolder有一个getBinding方法可以返回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
当一个variable或者observable改变了,在下一帧到来之前,Binding会被调度进行改变.但是有时候,Binding必须立刻被执行,可以通过调用executePendingBindings()方法.
Background Thread
你可以在后台线程改变你的数据只要它不是集合.数据绑定将本地化每个变量和成员,为了避免并行的问题.
Attribute Setters
无论什么时候,一旦被绑定的值发生变化,产生的Binding类必须结合绑定表达式在视图上面调用setter方法.数据绑定框架有多种方法决定调用哪个方法来设置值.
Automatic Setters
针对一个属性值,数据绑定试图找到setAttribute这个方法.不用关心属性命名空间,只要关心属性名字.
例如:和TextView的android:text关联的表达式将会去寻找setText(...)方法.如果表达式返回一个int,数据绑定将会寻找setText(int)方法.在表达式的返回值上面一定要小心,必须要的时候请进行转换.请注意即使没有对应的属性值数据绑定也能工作,你很简单就能创建属性为任何setter通过使用数据绑定.例如:辅助DrawerLayout没有任何属性,而是有很多setter.你可以使用自动设置器来使用其中的一个:
Renamed Setters
有一些属性值的setters名字不规则.对对于这些方法,可以通过BindingMethods来进行属性和setter的关联.这必须和某个类关联,并且包含BindingMethod注释和重命名的方法.例如:android:tint通过下面的方法已经和setImageTintList(ColorStateList)进行关联而非setTint.
@BindingMethods({
@BindingMethod(type = "android.widget.ImageView",
attribute = "android:tint",
method = "setImageTintList"),
})
开发者不大需要重命名setter;android框架属性已经被实现.
Custom Setters
有些属性需要自定义绑定逻辑.例如:没有和android:paddingLeft关联的setter,而只有setPadding(left, top, right, bottom).通过新建带有BindingAdapter注释的静态绑定适配方法允许开发者自定义属性的setter.
针对android属性值一些BindingAdapters已经被创建.例如:paddingLeft有一个:
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
Binding adapters对其他类型的自定义非常有用.例如:自定义加载器能够开启一个线程来加载图片.
在存在冲突的时候开发者创建的绑定适配器会覆盖data binding的默认适配器,你也可以定义接受多个参数的适配器:
@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提供了imageUrl和error,并且imageUrl是string,error是drawable.
- 在匹配的时候命令空间的名字被忽略
- 你也可以为android命名空间写一个适配器
绑定适配器方法可能在处理器中会使用旧值.一个方法如果需要旧值和新值,旧值应该放在前面,后面跟着新值:
@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.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);
}
因为改变一个监听器也会影响另外一个,我们必须有三个不同的绑定适配器,两个单独的和一个两个的:
@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);
}
}
}
上面的例子较正常情况有点复杂,因为正常情况为了设置View.OnAttachStateChangeListener通常使用add,remove而不是set方法.
android.databinding.adapters.ListenerUtil工具类帮忙记录之前的监听器因为它们有可能从绑定适配器中被移除.
通过给OnViewDetachedFromWindow和OnViewAttachedToWindow添加@TargetApi(VERSION_CODES.HONEYCOMB_MR1)注释,数据绑定代码生成器知道监听器仅仅需要 Honeycomb MR1或者更新的设备上面生成,addOnAttachStateChangeListener(View.OnAttachStateChangeListener)也支持.
Converters
Object Conversions
当绑定表达式返回一个对象,将会从自动的,重命名的和自定义的setters中选择一个.对象将会被转成该setter的参数类型.
这个对于使用ObservableMaps来持有数据非常方便.例如:
userMap返回的对象将自动被转换成setText(CharSequence)参数类型.如果存在混乱的话,就需要开发者自己进行转换:
Custom Conversions
有时候在特定类型之间的转换是自动完成的.例如,在我们设置背景时:
这是背景接受Drawable,但是color是个整型.无论什么情况都期望Drawable,但是返回的却是整型,因此int应该被转成ColorDrawable.这个转换将被带有BindingConversion注释的静态方法完成.
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
请注意转换只发生在setter级别,不允许混合类型像以下:
Android Studio Support for Data Binding
Android Studio为data binding提供很多代码编辑功能支持.例如,它为data binding表达式提供以下功能:
- 语法高亮
- 表达式语法错误标记
- XML代码补全
- 引用,包括浏览(如,定位到声明)和快速文档
注意: 数组和一般的类型,如:Observable 类,即使在没错误的时候,也可能显示错误.
如果表达式提供默认值的话,预览界面会显示默认值.在下面来自于一个布局xml文件的例子摘录中,预览界面将会在TextView中默认显示PLACEHOLDER文本
如果你需要在项目设计阶段显示默认值,你也能使用tools属性代替默认表达式值,正如 Designtime Layout Attributes 所描述.