在介绍DataBinding之前,肯定要先学会搭建使用它的环境。鉴于是2015年发布的,现在大家androidstudio的版本估计都在1.5以上了,就在1.5的版本上搭建和介绍吧,1.5之前的大家可以在网上搜索,因为我看网上大多数是介绍的1.3的使用方法,还没有很多对于1.5之后的环境搭建方法。
环境搭建
Android 的 Gradle 插件版本不低于 1.5.0-alpha1:
classpath 'com.android.tools.build:gradle:1.5.0'
然后修改对应模块(Module)的 build.grade:
android {
....
dataBinding {
enabled = true
}
}
注意:Android stuido 的版本一定要大于1.3,而且Android Studio目前对binding对象没有自动代码提示,只会在编译时进行检查。就是这么简单,但是1.3及以前的版本,对于环境的搭建,可能就会麻烦一点(没事1.3的环境搭建方法,网上多得是)。
Data Binding库不仅灵活而且广泛兼容- 它是一个support库,因此你可以在所有的Android平台最低能到Android 2.1(API等级7+)上使用它。
Binding数据
默认情况下,一个Binding类会基于layout文件的名称而产生,将其转换为Pascal case(译注:首字母大写的命名规范)并且添加“Binding”后缀。上述的layout文件是activity_main.xml
,因此生成的类名是ActivityMainBinding
。此类包含从layout属性到layout的Views中所有的bindings(例如user
变量),并且它还知道如何给Binding表达式分配数值。创建bindings的最简单的方式是在inflating(译注:layout文件与Activity/Fragment的“链接”)期间如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
User user = new User("Test", "User");
binding.setUser(user);
}
就是这样,运行app后,你将会看到Test User。或者你可以通过如下获取View:
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
如果你在ListView或者RecyclerView adapter使用Data Binding时,你可能会使用:
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup,
false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
深入 Layout 文件
Import
零个或多个import
元素可能在data
元素中使用。这些只用在你的layout文件中添加引用,就像在Java中:
<data>
<import type="android.view.View"/>
</data>
现在,View可以使用你的Binding表达式:
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
当类名有冲突时,其中一个类名可以重命名为alias:
:
<import type="android.view.View"/>
<import type="com.example.real.estate.View"
alias="Vista"/>
这样,在该layout文件中Vista
对应com.example.real.estate.View
,而View
对应android.view.View
。导入的类型可以在Variable和表达式中使用作为引用来使用:
<data>
<import type="com.example.User"/>
<import type="java.util.List"/>
<variable name="user" type="User"/>
<variable name="userList" type="List<User>"/>
</data>
注意:Android Studio还没有处理imports,所以自动导入Variable在你的IDE不能使用。您的app仍会正常编译,你可以在您的Variable定义中使用完全符合规定的名称来解决该IDE问题。
<TextView
android:text="@{((User)(user.connection)).lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
导入的类型还可以在表达式中使用static属性和方法:
<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"/>
就像在Java中,java.lang。*
是自动导入的。
Variables
在data
中可以使用任意数量的variable
元素。每一个variable
元素描述了一个用于layout文件中Binding表达式的属性。
<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
类型在编译时检查,因此如果一个Variable实现了Observable或observable collection,这应该反映在类型中。(译注:需要查找资料来理解)如果variable是一个没有实现Observable接口的基本类或者接口,Variables不会被observed!
当对于多种配置有不同的layout文件时(如,横向或纵向),Variables会被合并。这些layout文件之间必须不能有冲突的Variable定义。
产生的Binding类对于每一个描述的Variables都会有setter和getter。这些Variables会使用默认的Java值 - null(引用类型)、0(int)、false(boolean)等等,直到调用setter时。
自定义 Binding 类名称
默认情况下,Binding类的命名是基于所述layout文件的名称,用大写开头,除去下划线(_)以及(_)后的第一个字母大写,然后添加“Binding”后缀。这个类将被放置在一个模块封装包里的databinding
封装包下。例如,所述layout文件contact_item.xml
将生成ContactItemBinding
。如果模块包是com.example.my.app
,那么它将被放置在com.example.my.app.databinding
。
Binding类可通过调整data
元素中的class
属性来重命名或放置在不同的包中。例如:
<data class="ContactItem">
...
</data>
在模块封装包的databinding包中会生成名为ContactItem
的Binding类。如果要想让该类生成在不同的包种,你需要添加前缀.
,如下:
<data class=".ContactItem">
...
</data>
在这个情况下,ContactItem
类直接在模块包种生成。或者你可以提供整个包名:
<data class="com.example.ContactItem">
...
</data>
Includes
通过使用application namespace以及在属性中的Variable名字从容器layout中传递Variables到一个被包含的layout:
<?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
两个layout文件中必需要有user
variable
表达式
常用表达式跟Java表达式很像,以下这些是一样的:
-
数学
+
-
/
*
%
-
字符串连接
+
-
逻辑
&&
||
-
二进制
&
|
^
-
一元运算
+
-
!
~
-
移位
>>
>>>
<<
-
比较
==
>
<
>=
<=
-
instanceof
-
分组
()
-
null
-
Cast
-
方法调用
-
数据访问
[]
-
三元运算
?:
示例:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
缺少的操作:
-
this
-
super
-
new
-
显式泛型调用
Null合并操作
-
??
- 左边的对象如果它不是null,选择左边的对象;或者如果它是null,选择右边的对象:android:text="@{user.displayName ?? user.lastName}"
函数上的写法如下:
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
属性引用
第一个已经在前边提到了a)Data Binding表达式
:JavaBean引用的简短格式。
当一个表达式引用一个类的属性,它仍使用同样的格式对于字段、getters以及ObservableFields。
android:text="@{user.lastName}"
避免 NullPointerException
Data Binding代码生成时自动检查是否为nulls来避免出现null pointer exceptions错误。例如,在表达式@{user.name}
中,如果user
是null,user.name
会赋予它的默认值(null)。如果你引用user.age
,age是int
类型,那么它的默认值是0。
集合
常用的集合:arrays、lists、sparse lists以及maps,为了简便都可以使用[]
来访问。
<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
使用正常的表达式来访问resources也是可行的:
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
格式化字符串和复数可以通过提供参数来判断
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"
当复数需要多个参数时,所有的参数都会通过:
Have an orange
Have %d oranges
android:text="@{@plurals/orange(orangeCount, orangeCount)}"
一些资源需要显式类型判断:
| 类型 |正常引用 | 表达式引用 |
|:-|:-|:-|
| String[] | @array | @stringArray |
| int[] | @array | @intArray |
| TypedArray | @array | @typedArray |
| Animator | @animator | @animator |
| StateListAnimator | @animator | @stateListAnimator |
| color int | @color | @color |
| ColorStateList | @color | @colorStateList |
5. Data 对象
任何Plain old Java object(POJO)可用于Data Binding,但修改POJO不会导致UI更新。Data Binding的真正能力是当数据变化时,可以通知给你的Data对象。有三种不同的数据变化通知机制:Observable
对象、ObservableFields
以及observable collections
。
当这些可观察Data对象绑定到UI,Data对象属性的更改后,UI也将自动更新。
Observable 对象
实现android.databinding.Observable
接口的类可以允许附加一个监听器到Bound对象以便监听对象上的所有属性的变化。
Observable
接口有一个机制来添加和删除监听器,但通知与否由开发人员管理。为了使开发更容易,一个BaseObservable
的基类为实现监听器注册机制而创建。Data实现类依然负责通知当属性改变时。这是通过指定一个Bindable
注解给getter以及setter内通知来完成的。
private static class User extends BaseObservable {
private String firstName;
private String lastName;
@Bindable
public String getFirstName() {
return this.firstName;
}
@Bindable
public String getFirstName() {
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类文件会在模块包内生成。如果用于Data类的基类不能改变,Observable
接口通过方便的PropertyChangeRegistry
来实现用于储存和有效地通知监听器。
Observable 字段
一些小工作会涉及到创建Observable类,因此那些想要节省时间或者几乎没有几个属性的开发者可以使用ObservableFields
。ObservableFields
是自包含具有单个字段的observable对象。它有所有基本类型和一个是引用类型。要使用它需要在data对象中创建public final字段:
private static class User extends BaseObservable {
public final ObservableField<String> firstName =
new ObservableField<>();
public final ObservableField<String> lastName =
new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
就是这样,要访问该值,使用set和get方法:
user.firstName.set("Google");
int age = user.age.get();
Observable 集合
一些app使用更多的动态结构来保存数据。Observable集合允许键控访问这些data对象。ObservableArrayMap
用于键是引用类型,如String
。
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
在layout文件中,通过String键可以访问map:
<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
用于键是整数:
ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);
在layout文件中,通过索引可以访问list:
<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进阶
动态Variables
有时,不知道具体的Binding类,例如,一个RecyclerView
适配器对layouts任意操作并不知道具体的Binding类。它仍然必需在onBindViewHolder
期间赋值给Binding。
在这个例子中,该RecyclerView
绑定的所有layouts有一个“item”的Variable。该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();
}
直接Binding
当一个variable或observable变化时,binding会在下一帧之前被计划要改变。有很多次,但是在Binding时必须立即执行。要强制执行,使用executePendingBindings()
方法。
后台线程
只要它不是一个集合,你可以在后台线程中改变你的数据模型。在判断是否要避免任何并发问题时,Data Binding会对每个Varialbe/field本地化。
7. 属性Setters
每当绑定值的变化,生成的Binding类必须调用setter方法。Data Binding框架有可以自定义赋值的方法。
自动Setters
对于一个属性,Data Binding试图找到setAttribute
方法。与该属性的namespace并不什么关系,仅仅与属性本身名称有关。
例如,有关TextView的android:text
属性的表达式会寻找一个setText(String)
的方法。如果表达式返回一个int
,Data Binding会搜索的setText(int)
方法。注意:要表达式返回正确的类型,如果需要的话使用casting
。Data Binding仍会工作即使没有给定名称的属性存在。然后,您可以通过Data Binding轻松地为任何setter“创造”属性。例如,DrawerLayout
没有任何属性,但大量的setters。您可以使用自动setters来使用其中的一个。
<android.support.v4.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}"/>
自定义转换
有时候转换应该是自动的在特定类型之间。例如,设置背景的时候:
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
这里,背景需要Drawable
对象,但颜色是一个整数。不管何时有Drawable
并且返回值是一个整数,那么整数类型会被转换为ColorDrawable
。这个转换是通过使用带有BindingConversion
注解的静态方法完成的:
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
注意:转换仅仅发生在setter级别,因此它是不允许以下混合类型:
<View
android:background="@{isError ? @drawable/error : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>