Data Binding Library
简介:
Data Binding Library 是一个灵活、兼容的 support library ,可以在Android2.1(API 7)以上使用。使用DataBing,Gradle的Android Plugin需要在1.5.0-alpha1以上。
Build Environment
首先需要在Android SDK manager中下载DataBing的library。然后在build.gradle中添加dataBindingg节点,如下:1 2 3 4 5 6 | android { .... dataBinding { enabled = true } } |
如果你的应用中有其他模块或依赖的库用到了Data Binding,也需要在buidl.gradle中配置。
Data Binding 的布局文件
第一个DataBinding表达式使用 DataBinding 后的布局文件与原来的布局文件差距很小,使用 DataBinding 后的布局文件的根布局是 layout ,并添加了 data 节点。布局格式如下
1 2 3 4 5 6 7 | <?xml version= "1.0" encoding= "utf-8" ?> <layout xmlns:android= "http://schemas.android.com/apk/res/android" > <data> <!-- import and variable--> </data> <!-- 使用Data Binding前的根布局 --> </layout> |
eg.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <?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> |
data 节点中的 user 变量描述了可能会在布局中用到的属性值。
DataBinding表达式写在 "@{}" 中。在下面的例子中,TextView的text被设置为 user 的 firstName
1 2 3 | <TextView android:layout_width= "wrap_content" android:layout_height= "wrap_content" android:text= "@{user.firstName}" /> |
Data对象 (ViewModel)
假设你有一个User的POJO
1 2 3 4 5 6 7 8 | public class User { public final String firstName; public final String lastName; public User(String firstName, String lastName) { this .firstName = firstName; this .lastName = lastName; } } |
这种对象的数据永不会改变。常用于应用中只读一次永之后不改变的数据。
也可以使用JavaBeans:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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; } } |
在 DataBinding 中上面两个User对象是一样的。 @{user.firstName} 会访问第一个类的firstName字段第二个类的
getFirstName() 方法。如果存在 firstName() 方法,也可能会访问 firstName() 方法。
绑定数据
默认情况下,Binding类会根据布局文件的名字自动生成。如 main_activity.xml 的布局文件会生成 MainActivityBinding 。一个简单的数据绑定如下:
1 2 3 4 5 6 7 8 | @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); } |
Event Handling
Data Binding 提供了两种处理事件的方法。
- Method References(方法引用) :你可以直接引用和 listener 方法签名一致的方法。当一个表达式被解析为 Method References , Data Binding 会把该方法包装成一个 listener 并设置到对应的 View 。如果表达式被解析为 null , Data Binding 会设置成对应的 listener 为 null 。
- Listener Bindings :也可以使用 lambda , Data Binding 总是会创建设置到 View 上的 listener ,当事件被分发到该 View 时 lambda 表达式会被解析。
与可以在 android:onClick 属性指定调用的 Activity 方法类似,事件可以直接绑定到 Handler 的方法上。后者的优势在于实在编译时处理,如果方法不存在或签名不一致会得到一个编译时错误。下面的例子展示了该用法:
1 2 3 | public class MyHandlers { public void onClickFriend(View view) { ... } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <?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> |
注意: Handler 中方法必须与监听器中方法参数及返回值一样。如上面的例子中 Handler 的方法为 public void onClickFriend(View view) , android:onClick 需要的监听器为 OnClickListener ,其方法为 public void onClick(View view)
Listener Bindings
Listener Bindings 只要求 Handler 中方法与监听器中的方法返回值一致即可。
1 2 3 | public class Presenter { public void onSaveClick(Task task){} } |
1 2 3 4 5 6 7 8 9 10 11 | <?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> |
在上面的例子中问我们并没有定义传递给 onClick(android.view.View)方法的view参数。 Listener bindings 提供了两种传递监听器参数的选择:你可以忽略所有的参数。如果你更喜欢使用带参数的,上面的例子也可以改为
1 | android:onClick="@{(view) -> presenter.onSaveClick(task)} |
或者也可以传递更多参数
1 2 3 | public class Presenter { public void onSaveClick(View view, Task task){} } |
1 | android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)} |
避免监听器混淆
Listener 表达式非常强大,可以让你写出更易读的代码。另一方面,复杂的 Listener 表达式会让你的布局变得难以读懂、管理。 Listener 表达式应该尽量简单,只从UI传递有效数据到Callback的方法中。所有的业务逻辑都应该该在Callback的方法中实现。
存在一些特有的点击事件,它们的点击事件的属性并不是 android:onClick 。下面的表格列举了对应的属性:
Class | Listener Setter | Attribute |
---|---|---|
SearchView | setOnSearchClickListener(View.OnClickListener) | android:onSearchClick |
ZoomControls | setOnZoomInClickListener(View.OnClickListener) | android:onZoomIn |
ZoomControls | setOnZoomOutClickListener(View.OnClickListener) | android:onZoomOut |
Layout Details(布局详情)
Imports可以在 节点使用 import 语法,与java相同 java.lang.* 也是自动导入的。
1 2 3 | <data> <import type= "android.view.View" /> </data> |
当有相同类名时,可以使用别名:
1 2 | <import type= "android.view.View" /> <import type= "com.example.real.estate.View" alias= "Vista" /> |
Variables
Variables 用来描述布局文件中的属性值。
1 2 3 4 5 6 | <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> |
在编译期间会对 variable 进行类型检查,只有实现了 Observable 接口或是一个 observable collection 的 variable 才会被观察,即当该 variable 的数据发生变化 UI 会更新。
为了适配不同的屏幕你可能会有多个布局文件, Data Binding 会合并这些布局文件中的 variable ,所以这些布局文件中的 variable 不能有混淆。
Data Binding 会为每个 variable 自动生成对应的 setter 和 getter 方法。 variable 在调用 setter 前都会有一个默认值:引用类型-> null 、 int -> 0 、布尔值-> false 。
Data Binding 会自动生成一个 context 的 variable 。它来值跟布局的 getContext() 。可以被用户的 variable 覆盖。
Includes
variable 在使用了 布局中的应用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <?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 和 contact.xml 布局文件都必须有 user variable
不支持 include 直接作为 merge 的直接子元素,如下面的布局:
1 2 3 4 5 6 7 8 9 10 11 12 13 | <?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中的一样
- Mathematical + - / * %
- String concatenation +
- Logical && ||
- Binary & | ^
- Unary + - ! ~
- Shift >> >>> <<
- Comparison == > < >= <=
- instanceof
- Grouping ()
- Literals - character, String, numeric, null
- Cast
- Method calls
- Field access
- Array access []
- T*ernary operator ?:
1 2 3 | android:text= "@{String.valueOf(index + 1)}" android:visibility= "@{age < 13 ? View.GONE : View.VISIBLE}" android:transitionName= '@{"image_" + id}' |
消失的操作
在Java中可用,但在 Databinding 中不可用的操作
- this
- super
- new
- Explicit generic invocation
1 | android:text= "@{user.displayName ?? user.lastName}" |
等于
1 | android:text= "@{user.displayName != null ? user.displayName : user.lastName}" |
Property Reference(属性引用)
无论Data采用的是那种定义方法,POJO、Java Beans或ObservableFields,在 DataBinding 的语法中引用都用下面
的方式。
1 | android:text= "@{user.lastName}" |
Avoiding NullPointerException(自动避免空指针异常)
Data Binding 生成的代码会自动检查 null 值,避免空指针异常。
例如 @{user.name} ,如果 user 为空, user,name 会被赋值为默认值( null )。如果是 int 类型的默认值为0。
Collections (集合)
常见的集合:arrays, lists, sparse lists, and maps可以通过 [] 访问。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <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]}" |
String Literals (字符串)
如果需要在 @{} 表达式内使用 "" ,布局的属性值需要使用 ''
1 | android:text= '@{map["firstName"]}' |
或者使用下面的方式
1 2 | android:text= "@{map[`firstName`}" android:text= "@{map['firstName']}" |
Resources (资源文件)
可以使用通常的语法在表达式中访问资源文件:
1 | android:padding= "@{large? @dimen/largePadding : @dimen/smallPadding}" |
格式化的字符串和 plurals
1 2 | android:text= "@{@string/nameFormat(firstName, lastName)}" android:text= "@{@plurals/banana(bananaCount)}" |
一些资源需要使用指定的引用方式
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 |
Data Objects (数据对象)
任何一个POJO都可以用于 Data Binding ,但是修改了一个并POJO不会更新UI。 Data Binding 的real power是让你的数据对象( Data Objects )发生数据改变时能够发出通知。这里有3种数据改变通知机制: Observable objects , observable fields , observable collections 。当一个实现了通知机制的数据对象的数据发生改变并且绑定到了UI,UI会自动更新。
Observable Objects (Observable对象)
Observable interface有一个添加和移除listeners的机制,但是发送通知取决于开发者。为了方便开发, Data Binding 提供了一个 BaseObservable 类, BaseObservable 实现了listener注册机制。实现类要负责在数据改变时发出通知,这需要通过在 getter 方法加上 @Bindable 注解并且在 setter 方法中发出通知。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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 与 R 类似。
ObservableFields
Data Binding 还提供了另外更细粒度、更方便的创建 Observable 类的方式: ObservableFields , ObservableBoolean , ObservableByte , ObservableChar , ObservableShort , ObservableInt , ObservableLong , ObservableFloat , ObservableDouble , and ObservableParcelable 。eg.
1 2 3 4 5 6 7 | private static class User { public final ObservableField<String> firstName = new ObservableField<>(); public final ObservableField<String> lastName = new ObservableField<>(); public final ObservableInt age = new ObservableInt(); } |
如何访问:
1 2 | user.firstName.set( "Google" ); int age = user.age.get(); |
Observable Collections
一些App使用集合保存数据。 Observable Collections 允许使用通过 键 来访问数据。
ObservableArrayMap
用于 键 是 引用类型 ,如 String
1 2 3 4 | ObservableArrayMap<String, Object> user = new ObservableArrayMap<>(); user.put( "firstName" , "Google" ); user.put( "lastName" , "Inc." ); user.put( "age" , 17); |
在布局文件中的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 | <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
键 是 int
1 2 3 4 | ObservableArrayList<Object> user = new ObservableArrayList<>(); user.add( "Google" ); user.add( "Inc." ); user.add(17); |
在布局文件中的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <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" /> |
Generated Binding(生成Binding)
上文已经说过生成的 Binding 类名由布局文件名决定。Creating(创建)
Binding 类应该尽可能早地创建,绑定layout的方式有多种,都是通过调用 Binding 类的静态方法。如:
1 2 3 4 5 6 | MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater); MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false ); MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot); ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId, parent, attachToParent); ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId); |
Views With IDs(带id的View)
Data Binding 会为布局文件中每个带ID的View生成一个 public final 的字段。这可以让替代 findViewById 以更快的方式访问View:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <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> |
生成的Binding类中会有:
1 2 | public final TextView firstName; public final TextView lastName; |
ViewStubs
布局文件中的 ViewStub 经过binding之后会转换成 ViewStubProxy 。
简单用代码说明一下,xml 文件与之前的代码一样,根节点改为 layout,在 LinearLayout 中添加一个 ViewStub,添加 ID。
1 2 3 4 5 6 7 8 9 10 11 12 | <?xml version= "1.0" encoding= "utf-8" ?> <layout xmlns:android= "http://schemas.android.com/apk/res/android" > <RelativeLayout android:layout_width= "match_parent" android:layout_height= "match_parent" > <ViewStub android:id= "@+id/view_stub_test" android:layout= "@layout/stub" android:layout_width= "match_parent" android:layout_height= "match_parent" /> </RelativeLayout> </layout> |
在 Java 代码中获取 binding 实例,为 ViewStubProy 注册 ViewStub.OnInflateListener 事件:
1 2 3 4 5 6 7 8 9 10 | binding.viewStubTest.setOnInflateListener( new ViewStub.OnInflateListener() { @Override public void onInflate(ViewStub stub, View inflated) { StubBinding stubBinding = DataBindingUtil.bind(inflated); ViewStubViewModel model = new ViewStubViewModel(); model.setName( "boco" ); stubBinding.setStubModel(model); } }); binding.viewStubTest.getViewStub().inflate(); |
Advanced Binding(高级绑定)
Dynamic Variables(动态设置Variables)
有时候,我们并不知道具体的Binding类型,如在 RecyclerView.Adapter 中可能会有多个 layout ,我们必须在 onBindViewHolder(VH, int) 进行赋值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | public static class BindingHolder extends RecyclerView.ViewHolder { private ViewDataBinding binding; public BindingHolder(View itemView) { super(itemView); } public ViewDataBinding getBinding() { return binding; } public void setBinding(ViewDataBinding binding) { this .binding = binding; } } @Override public BindingHolder onCreateViewHolder(ViewGroup viewGroup, int i) { ViewDataBinding binding = DataBindingUtil.inflate( LayoutInflater.from(viewGroup.getContext()), R.layout.list_item, viewGroup, false ); BindingHolder holder = new BindingHolder(binding.getRoot()); holder.setBinding(binding); return holder; } @Override public void onBindViewHolder(BindingHolder holder, int position) { User user = users.get(position); holder.getBinding().setVariable(BR.user, user); holder.getBinding().executePendingBindings(); } |
Immediate Binding(立即绑定)
当一个variable或者observable发生了改变,数据绑定会在下一帧之前发生但不是立刻发生,如果你希望数据绑定立刻发生需要使用 executePendingBindings() 方法,eg.
1 2 3 4 5 | public void onBindViewHolder(BindingHolder holder, int position) { final T item = mItems.get(position); holder.getBinding().setVariable(BR.item, item); holder.getBinding().executePendingBindings(); } |
Background Thread
你可以在后台线程中改变不是集合的数据,Data binding will localize each variable / field while evaluating to avoid any concurrency issues.
Attribute Setters (属性赋值)
自动赋值有了 Data Binding ,即使属性没有在 declare-styleable 中定义,我们也可以通过 xml 进行赋值操作。
1 2 3 4 | <android.support.v4.widget.DrawerLayout android:layout_width= "wrap_content" android:layout_height= "wrap_content" app:drawerListener= "@{fragment.drawerListener}" /> |
在上面的例子中 app:drawerListener="@{fragment.drawerListener} 会调用DrawerLayout的 setDrawerListener(DrawerListener listener) 方法具体的方法参数类型和 @{} 中的参数类型一致。
重命名
1 2 3 4 5 6 7 | <Button android:id= "@+id/email_sign_in_button" android:layout_width= "match_parent" android:layout_height= "wrap_content" app:myText= "@{login.name}" android:onClick= "@{loginHandler.onClick}" /> |
在上面的xml对应的ViewModel中加入如下注解, app:myText="@{login.name} 便会调用Button的setText方法。
1 2 3 4 5 | @BindingMethods({ @BindingMethod(type = android.widget.Button. class , attribute = "myText" , method = "setText" ), }) |
自定义
1 2 3 4 5 6 7 8 9 10 11 12 | ViewModel中的方法 @BindingAdapter({ "app:myText" }) public static void showText(Button button,String newV,String oldV){ button.setText(newV); Log.w(TAG, "new=" +newV+ "--oldV=" +oldV); } or @BindingAdapter({ "app:myText" }) public static void showText(Button button,String newV){ button.setText(newV); Log.w(TAG, "new=" +newV); } |
对应得xml代码:
1 2 3 4 5 6 7 | <Button android:id= "@+id/email_sign_in_button" android:layout_width= "match_parent" android:layout_height= "wrap_content" app:myText= "@{login.name}" android:onClick= "@{loginHandler.onClick}" /> |
转换器 (Converters)
在 xml 中为属性赋值时,如果变量的类型与属性不一致,通过 DataBinding 可以进行转换。例如,下面代码中如果要为属性 android:background 赋值一个 int 型的 color 变量:
1 2 3 4 5 | <View android:background= "@{isError.get() ? @color/red : @color/white}" android:layout_width= "match_parent" android:layout_height= "wrap_content" app:layout_height= "@{height}" /> |
只需要定义一个标记了 @BindingConversion 的静态方法即可( 方法的定义位置可以随意 ):
1 2 3 4 | @BindingConversion public static ColorDrawable convertColorToDrawable( int color) { return new ColorDrawable(color); } |
来自:http://www.jianshu.com/p/e94141bbb8e4
收藏的用户(0) X
正在加载信息~
推荐阅读
Observable取代AsyncTask和AsyncTaskLoader
最新回复 (0)
站点信息
- 文章2300
- 用户1336
- 访客10863472
每日一句
True success inspires others to act.
真正的成功是激励他人行动。
真正的成功是激励他人行动。
新会员