android Data Binding Library
参考
田浩浩_DockOne : Data Binding(数据绑定)用户指南libraries/data-binding/
配置环境
Android 2.1 (API level 7+)
//app build.gradle
android {
....
dataBinding {
enabled = true
}
}
使用
//1,定义属性
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;
}
}
//2,xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="com.test.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}"/>
</LinearLayout>
</layout>
//Activity
@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);
}
// ListView or RecyclerView adapter
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
Data Binding Layout文件
- Data Binding表达式 layout data @{}
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="user" type="com.test.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}"/> </LinearLayout> </layout> - Data对象 POJO,JavaBeans
//plain-old Java Object(POJO) //@{user.firstName} -> user.firstName public class User { public final String firstName; public final String lastName; public User(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } } //JavaBeans //@{user.firstName} -> user.getFirstName() 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; } } - Binding类 LayoutNameBinding
@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); } // MyLayoutBinding binding = MyLayoutBinding.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,alias
//导入类 <data> <import type="android.view.View"/> </data> //使用 <TextView android:text="@{user.lastName}" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/> //Android Studio还没有处理imports,所以自动导入Variable在你的IDE不能使用 <TextView android:text="@{((User)(user.connection)).lastName}" android:layout_width="wrap_content" android:layout_height="wrap_content"/> //别名 <import type="android.view.View"/> <import type="com.example.real.estate.View" alias="Vista"/> //导入的类型可以在Variable和表达式中使用作为引用来使用 <data> <import type="com.example.User"/> <import type="java.util.List"/> <variable name="user" type="User"/> <variable name="userList" type="List<User>"/> </data> //导入的类型还可以在表达式中使用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"/> - Variables
- 自定义Binding类名称
//默认包 //com.example.app.databinding.ContactItem <data class="ContactItem"> ... </data> // <data class="com.example.ContactItem"> ... </data> - Includes
//传递Variables 容器layout -> 被包含的layout //xmlns:bind include bind: <?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
- 示例:
android:text="@{String.valueOf(index + 1)}" android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}" android:transitionName='@{"image_" + id}' - 数学 + - / * %
- 字符串连接 +
- 逻辑 && ||
- 二进制 位运算 & | ^
- 移位 >> >>> <<
- 一元运算 + - ! ~
- 比较 == > < >= <=
- instanceof
- 分组 ()
- null
- Cast
- 方法调用
- 数据访问 []
- 三元运算 ? :
- 示例:
- 缺少的操作:
- this
- super
- new
- 显式泛型调用
- Null合并操作 ??
//?? //如果左边的对象不是null,选择左边的对象; //如果左边的对象是null,选择右边的对象: android:text="@{user.displayName ?? user.lastName}" //等价于 android:text="@{user.displayName != null ? user.displayName : user.lastName}" - 属性引用
- android:text="@{user.lastName}"
- 当一个表达式引用一个类的属性,它仍使用同样的格式对于字段、getters以及ObservableFields。
- 避免 NullPointerException 赋予类型默认值
- 集合 []
//常用的集合: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]}" - 字符串 "@{'firstName'}"
//使用单引号包含属性值时,在表达式中使用双引号 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
- 常用表达式 java
Data对象 数据变化通知机制
- Observable对象 android.databinding.Observable接口
//Observable接口有一个机制来添加和删除监听器,但通知与否由开发人员管理 //BaseObservable : 实现监听器注册机制的一个基类 //当属性改变时, 由Data实现类负责通知 //指定Bindable注解给getter //setter内通知该属性改变 notifyPropertyChanged() private static class User extends BaseObservable { private String firstName; private String lastName; //指定Bindable注解给getter @Bindable public String getFirstName() { return this.firstName; } @Bindable public String getFirstName() { return this.lastName; } public void setFirstName(String firstName) { this.firstName = firstName; //setter内通知该属性改变 notifyPropertyChanged(BR.firstName); } public void setLastName(String lastName) { this.lastName = lastName; notifyPropertyChanged(BR.lastName); } } //在编译期间,Bindable注解在BR类文件中生成一个Entry。 //BR类文件会在模块包内生成。 //如果用于Data类的基类不能改变,Observable接口通过PropertyChangeRegistry //来实现用于储存和有效地通知监听器。 - Observable字段 ObservableFields
//ObservableFields是自包含具有单个字段的observable对象。 //它有所有基本类型和一引用类型。要使用它需要在data对象中创建public final字段: private static class User { 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集合 ObservableArrayMap ObservableArrayList
//ObservableArrayMap 通过String键访问map ObservableArrayMap<String, Object> user = new ObservableArrayMap<>(); user.put("firstName", "Google"); user.put("lastName", "Inc."); user.put("age", 17); <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索引访问list ObservableArrayList<Object> user = new ObservableArrayList<>(); user.add("Google"); user.add("Inc."); user.add(17); <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生成
- 创建 在inflation之后就立马创建
//在Binding类上使用静态方法.inflate方法载入View的层次结构并且绑定到它 MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater); MyLayoutBinding binding = MyLayoutBinding.inflate(LayoutInflater, viewGroup, false); //使用不同的机制载入layout,可分开绑定 MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot); //有时Binding不能提前知道,可以使用DataBindingUtil类来创建Binding ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId, parent, attachToParent); ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId); - 带ID的Views 在Binding中生成相应的常量引用
//在layout中对于每个带ID的View会生成一个public final字段。 //Binding在View层次结构上做单一的传递,提取带ID的Views <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:id="@+id/firstName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.firstName}" /> <TextView android:id="@+id/lastName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.lastName}" /> </LinearLayout> </layout> //生成如下的Binding类 public final TextView firstName; public final TextView lastName; - Variables 在Binding中生成setters和getters
<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> //在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
Binding进阶
- 直接Binding executePendingBindings()
- When a variable or observable changes, the binding will be scheduled to change before the next frame. There are times, however, when binding must be executed immediately. To force execution, use the executePendingBindings() method.
- 动态Variables
//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(); } - 后台线程
- 只要它不是一个集合,你可以在后台线程中改变你的数据模型。在判断是否要避免任何并发问题时,Data Binding会对每个Varialbe/field本地化。
属性Setters
- 自动Setters : attribute="@{(int)value}" -> 寻找setAttribute(int);
//DrawerLayout没有任何属性,但有大量的setters <android.support.v4.widget.DrawerLayout android:layout_width="wrap_content" android:layout_height="wrap_content" app:scrimColor="@{@color/scrim}" app:drawerListener="@{fragment.drawerListener}"/> - 重命名的Setters @BindingMethods
//一些有setters的属性按名称并不匹配。 //对于这些方法,属性可以通过BindingMethods注解相关联。 //这必须与一个包含BindingMethod注解的类相关联,每一个用于一个重命名的方法。 //例如,android:tint属性与setImageTintList相关联,而不与setTint相关。 @BindingMethods({ @BindingMethod(type = "android.widget.ImageView", attribute = "android:tint", method = "setImageTintList"), }) - 自定义Setters @BindingAdapter
//有些属性需要自定义绑定逻辑。 //例如,对于android:paddingLeft属性并没有相关setter。 //相反,setPadding(left, top, right, bottom)是存在在。 //一个带有BindingAdapter注解的静态绑定适配器方法允许开发者自定义setter如何对于一个属性的调用。 @BindingAdapter("android:paddingLeft") public static void setPaddingLeft(View view, int padding) { view.setPadding(padding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()); }
转换
- 对象转换
//当从Binding表达式返回一个对象,相应setter会从自动、重命名以及自定义的setters之中选择出来 //该对象将被转换为所选择的setter的参数类型。 android:text='@{userMap["lastName"]}' //android:text="" -> setText(CharSequence) //userMap["lastName"] -> object -> charSequence - 自定义转换 @BindingConversion
android:background="@{isError ? @color/red : @color/white}" //Drawable : ColorDrawable <-- Int @BindingConversion public static ColorDrawable convertColorToDrawable(int color) { return new ColorDrawable(color); } //转换仅仅发生在setter级别,因此它是不允许以下混合类型 android:background="@{isError ? @drawable/error : @color/white}"
Android Studio支持
- 数组以及通用类型,比如说Observable类,可能会显示错误事实上并没有错误。