Data Binding Library
作用 : 在声明式布局的时候,最小化 页面布局和应用逻辑 之间的glue code (胶水代码)。
前提
1. Android 2.1(API level 7+)
2. Gradle 1.5.0- alpha 1
环境搭建
- 确保已经安装了绑定库
- 配置gradle脚本
- 开发工具 android studio 1.3 …
android {
dataBinding{
enabled = true
}
}
注意: 如果应用程序模块中使用了数据绑定库,则APP也需要配置gradle脚本
Data Binding Compiler V2
在 Android gradle 插件 3.1.0 Cannary 6 中 附带了 可选的新 编译器。如果想使用新的DataBinding 编译器 ,可以在 gradle.properties 文件中添加 android.databinding.enableV2=true
In compiler V2:
- VIewBinding 系列的类 将在java compiler 编译之前 由 Android Gradle Plugin 生成。这样可以避免 因为不相关的原因导致 java compiler 失败 ,从而导致得到太多误报错误。
- 在V1中 ,binding 系列的类将会在app编译完成后再次生成(去分享生成的代码并关联到 常量‘BR’ 和 ‘R’ 文件 )。在V2 中,绑定库为多模块项目 将 保留 生成的binding 系列类和映射信息 从而 显著提高 数据绑定性能。
注意: 新的V2编译器 是向后不兼容,所以使用v1编译的库不能 被V2使用,反之亦然。
V2 删除了一些 很少使用的功能 去允许如下更改:
* 在V1,一个应用程序可以在依赖中 提供 可以重写覆盖 原适配器的 binding adapters ,但在 V2 中 binding适配器 只能在 本身的模块/应用中和它的依赖中产生作用
* 先前,如果一个布局文件在两个或者多个不同的资源配置中 却被具有相同 id 但不同类的 VIew 所装载,DataBinding 将会找到最共同的父类。在V2,这将会默认配置给VIew 当类型在配置文件中不匹配。HeHe
* 在V2编译器下,不同模块不能再manifest清单中配置相同的包名,因为DataBinding 将会使用 包名来生成绑定映射器。
Data Binding Layout Files
数据绑定布局文件略有不同,它是以layout
为root tag 紧随着 数据元素 data
和视图 根元素view root element。 视图根元素就是原来不适用数据绑定框架的布局文件。
<?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>
布局文件中可以使用 包括数据描述属性 的 变量标签 。for example:
<variable name="user" type="com.example.User"/>
常用的表达式 方式 @{}
数据对象
数据对象可以是 普通 POJO类 和 JavaBeans类。
假设引用 @{viewModel.useName} 等价于 公有属性 userName 等价于 公有get方法 getUserName() 等价于 userName() 方法。
绑定数据
默认的 ,一个Bainding 类 会基于 布局文件名 生成,翻译布局文件名以 Pascal case (命名方法) 并且以”Binding” 结尾。
创建Binding的最简单方法是在inflating 过程中执行例如:
@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 = MainActivityBinding.inflate(getLayoutInflater());
在listview 或者recycleView中使用DataBinding
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
事件处理
有两种方式去处理事件:
1. Method References:(方法引用): 开发者可以引用符合监听器方法签名的方法,当表达式被评估为方法引用的时候,数据绑定框架将会把方法引用和所有者对象包装在一个监听器中,并且将监听器赋给视图。如果表达式为空 ,将不会生成监听器。
2. 监听器binding ,用一些Lanbda表达式,原理与方法引用类似,当事件分发时,监听器将评估lambda表达式。Lambda ( 提取对应监听方法的入参 ) -> function(自定义传参 (可使用入参 变量 ))
避免复杂的监听器
原则是 是布局文件可阅读,可维护性更高。
存在一些特殊的点击事件,它们需要的别的属性 而不是 android:onClick
去避免冲突.
如下:
Class Listener Setter Attribute
SearchView setOnSearchClickListener(View.OnClickListener) android:onSearchClick
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomIn
ZoomControls setOnZoomOutClickListener(View.OnClickListener) android:onZoomOut
布局细节
import 0..more
类似java中引用类。
类名冲突
<import type="android.view.View"/>
<import type="com.example.real.estate.View"
alias="Vista"/>
导入的类型可以用作于变量和类型引用
<data>
<import type="com.example.User"/>
<import type="java.util.List"/>
<variable name="user" type="User"/>
<variable name="userList" type="List<User>"/>
</data>
Variables
变量类型将会在编译的时候被检查,如果类型体现了它们 实现了,Observable 或者observable.collection 接口,那么变量将可被观察。
生成的binding类将会有setter getter 方法对每一个被描述的变量。在setter方法被执行前,默认值将会是java 默认值,,引用对象 == null int == 0 Boolean == false
binding表达式将会生成一个名叫 context的变量,变量的值是从 View的 getContext() 方法中获取.
Custom Binding Class Names(自定义 绑定类 名称)
默认地,一个绑定类将会基于布局文件名 生成 例如 : contact_item.xml —> ContactItemBinding类,然后该类放在 databinding包下。
但是Binding类可以放在不同的包下可自定义命名
<data class="包名.类名">
</data>
Include
用于 布局文件的引用
不支持引用布局作为合并元素的直接子元素。
表达式语言
与java有共同特征的
Mathematical 数学运算符 + - / * %
String 字符串拼接 concatenation +
Logical 逻辑运算 && ||
Binary 二进制运算 & | ^
Unary 一元运算符 + - ! ~
Shift 位运算 >> >>> <<
Comparison 比较运算符 == > < >= <=
instanceof 继承关系
Grouping ()
Literals - character, String, numeric, null
Cast
Method calls
Field access
Array access []
Ternary operator ?:
缺少的方法
java中可以用 lambda中无法使用
- this
- super
- new
- Explicit generic invocation 显示泛型调用
空值 合并操作
空合并操作符 ??
选择左边的值,如果左值不为空 否则 右值
android:text="@{user.displayName ?? user.lastName}"
等价 android:text="@{user.displayName != null ? user.displayName : user.lastName}"
避免空指针异常
生成的数据绑定代码 将会自动检查空值和避免空指针异常。 当遇到空值 databinding将会使用各类型默认值代替
集合操作
公共集合包括 arrays lists sparse lists 和 map ,也许会通过操作符 []
来方便使用
举例:
<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 字符串文字
android:text='@{map["firstName"]}'
android:text="@{map[`firstName`}"
android:text="@{map['firstName']}"
Resources 资源文件
使用普通的表达式作为表达式的一部分是可以获取资源文件
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
格式化字符串 和 复数是可以通过 参数来评估的
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 Object
任何 POJO 都可以用于 DataBinding ,但是修改一个POJO 不会产生UI更新。Databinding的真正能力是给你的数据对象们通知的能力在器数据被修改时。 有3种 不同的 数据修改通知机制。Observable objects、observable fields 和 observable collections
- Observable Object
实现Observable 接口的类 允许 绑定一个 附加的侦听器 到绑定的对象上,去监听所有的属性变化。
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 class 文件中生成一个条目。BR文件将会生成在Module 包下。
如果 数据类的基类不能被修改,通过使用简便的PropertyChangeRegistry实现的 Observable 接口更有效的存储和通知监听器。
- Observable fields
当创建观察量的工作量很小,开发者可能会为了缩小开发时间,而采用ObservableField方式和其同级对象 ObservableBoolean ObservableByte ObservableChar ObservableShort ObservableInt ObservableLong ObservableFloat ObservableDouble ObservableParcelable
可观察属性将会生成一个具有单个字段的可观察对象。原始版本将会在访问阶段 避免装箱和拆箱操作。为了使用将会在数据类中使用FInal field
for example:
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();
- observable collection
一些应用使用更加动态的结构去存储数据对象。可观察集合可以通过keys值来获取其数据对象
ObservableArrayMap 是一个有效地方法当keys是索引对象 ,比如是 String
code for example:
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
生成Binding Generated Binding
生成的Binding类 会根据xml 文件名 和配置生成 ,,继承自 ViewDataBinding。
Creating 生成过程
Binding的应该立马在Inflation 视图文件后生成 以确保 在Layout中使用表达式bingding View而不影响视图结构。
有几种方法去绑定 Layout,最常见的方法是使用 Binding Class的静态类方法。静态类方法的Inflate方法将会一次性 infaltes 视图结构 完成绑定。
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);
Sometimes the binding cannot be known in advance. In such cases, the binding can be created using the DataBindingUtil class:
parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
Views With IDs
如果布局中的View 包含Id 属性 ,则框架会为他们各自生成一个公有常量。Binding将会在视图上进行单个传递,通过ID获取视图。这种机制对一些视图将会比使用findVIewById更加高速。
for Example:
<TextView id = "firstName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
在bingding中 生成
public final TextView firstName;
使用:
binding.firstName;
Variables
每一个变量 将会被给与一个访问方法
<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);
Advanced Binding 高级Binding
Dynamic Variables
有时候 特定的binding类将不会被知道,比如在RecycleView.Adapter 操作针对任意布局将不会知道其对应的指定Binding Class。它必须在onBindViewHolder 中 分配Binding值。forExample
使用 BindingHolder 代替ViewHolder 使用期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 立刻Binding
当变量 或者 可观察对象 改变时,,binding 将会计划改变 在下一帧的时候。有时候,必须立刻binding ,解决方法 是使用 executePendingBindings() 方法
Background Thread
牛逼的功能
You can change your data model in a background thread as long as it is not a collection. Data binding will localize each variable / field while evaluating to avoid any concurrency issues.
Attribute Setters
Automatic Setters 自动赋值
对于属性,data binding 尝试 去寻找属性的setAttribute 方法。相对于属性名称本身,属性的命名空间本部重要。
举一个例子: 在表达式中关联到 TextView的属性 android:text ,databinding 将会去寻找 setText方法。如果表达式返回的是一个 int ,databinding 将会去寻找 setText(int) 方法。请注意表达式返回的类型是否正确,必要时要进行 转换操作。注意: data Binding 将会依旧工作 即使 关联的View不存在 命名的属性。所以你可以简单=地生成一些属性 进行setter 通过 databinding 。举例:DrawerLayout 拥有大量没有xml 属性的setter 方法 :
<android.support.v4.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}"/>
Renamed Setters 重命名Setter
一些attributes有拥有setter方法但不符合命名规则,对于这些方法,这些属性可以通过 BindingMethods 注解来关联。这必须和一个类相关联并且包含BindingMethod 方法。举例 android:tint 其真正关联的方法是 setImageTintList(ColorStateList)方法 而不是 setTint.
@BindingMethods({
@BindingMethod(type = "android.widget.ImageView",
attribute = "android:tint",
method = "setImageTintList"),
})
Custom Setters 自定义Setter
终于到接触过的@BindingAdapter
Some attributes need custom binding logic. For example, there is no associated setter for the android:paddingLeft attribute. Instead, setPadding(left, top, right, bottom) exists. A static binding adapter method with the BindingAdapter annotation allows the developer to customize how a setter for an attribute is called.
The android attributes have already had BindingAdapters created. For example, here is the one for paddingLeft:
@BindingAdapter(“android:paddingLeft”)
public static void setPaddingLeft(View view, int padding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
Binding adapters are useful for other types of customization. For example, a custom loader can be called off-thread to load an image.
Developer-created binding adapters will override the data binding default adapters when there is a conflict.
You can also have adapters that receive multiple parameters.
@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);
}
Converters 转换器
## Object Conversions 对象转换器
当一个对象从表达式中返回,将会自动从renamed 和 传统setters 选择一个setter方法,该对象将会转换成所选setter所需要的参数类型。
有时 转换时自动进行的。比如 background方法:背景将会是一个Drawable ,但是给的Color 是一个Int 所以,会需要Int 转换成ColorDrawable
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
Android Studio Support for Data Binding
Android Studio supports many of the code editing features for data binding code. For example, it supports the following features for data binding expressions:
Syntax highlighting
Flagging of expression language syntax errors
XML code completion
References, including navigation (such as navigate to a declaration) and quick documentation
Note: Arrays and a generic type, such as the Observable class, might display errors when there are no errors.