Data Binding Library
数据绑定库编写声明式布局,尽量减少绑定应用程序逻辑和布局所需的代码,减少布局绑定相关代码
build environment
1
2
3
4
5
6android{
...
dataBinding {
enabled = true
}
}Data Binding Compiler V2
1
2android.databinding.enableV2=true
向后不兼容
Data Binding 布局文件
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="java.util.List" />
<import type="android.view.View" />
<import type="com.willkernel.www.databindingdemo.User" />
<import type="com.willkernel.www.databindingdemo.DoSomething" />
<!--<variable-->
<!--name="view"-->
<!--type="View" />-->
<variable
name="user"
type="User" />
<variable
name="userClick"
type="com.willkernel.www.databindingdemo.MainActivity.UserClick" />
<variable
name="ds"
type="DoSomething" />
</data>
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{userClick::onUserClick}"
android:text="@{user.firstName}" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{() -> userClick.onUserClick(user)}"
android:text="@{user.firstName}" />
<!--android:onClick="@{userClick::onUserClick}"-->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{(v) -> userClick.onUserClick(v,user)}"
android:text="@{user.firstName}" />
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onCheckedChanged="@{(cb,isChecked) -> userClick.onCheckedChanged(user,isChecked)}"
android:text="@{user.lastName}" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onLongClick="@{(v)->userClick.onLongClick(v,user)}"
android:text="@{user.lastName}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"
android:visibility="@{user.isAdult ? View.VISIBLE:View.INVISIBLE}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{DoSomething.capital(user.firstName)}" />
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
</layout>data标签下的variable描述将会在布局中使用的属性
1
<variable name="user" type="com.example.User"/>
使用”@{}” 语法获取属性
1
2
3<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>三种情况databinding都可以获取属性
1
2
3
4
5
6
7
8
9
101. public final String firstName;
2. private final String firstName;
public String getFirstName() {
return this.firstName;
}
3. private final String firstName;
public String firstName() {
return this.firstName;
}
数据绑定,默认基于布局文件名称生成绑定类,将其转换为Pascal实例并添加Binding后缀,例如布局文件activity_main.xml,生成的类为MainActivityBinding,持有布局所有属性的绑定方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
User user = new User();
user.firstName = "firstName";
user.lastName = "lastName";
user.isAdult = true;
binding.setUser(user);
binding.setUserClick(new UserClick());
}
也可以这样获取View
ActivityMainBinding binding =ActivityMainBinding.inflate(getLayoutInflater());
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);ListView,RecyclerView适配器中绑定
1
2
3ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);如果通过其他方式填充的布局,需要调用bind方法
1
MyLayoutBinding binding =MyLayoutBinding.bind(viewRoot);
另外的方式
1
2
3ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);布局中的views将没有设置id的会生成private final ID,设置id的会生成public final ID ,比findViewById速度更快
生成的方法
variable
1
2
3
4
5
6
7
8
9
10
11
12
13<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);ViewStubs
因为在显示后会从view层级中消失,所以view的binding对象也会被回收1
2
3
4
5
6
7
8
9
10
11
12
13@NonNull
public final android.databinding.ViewStubProxy viewStub;
binding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub stub, View inflated) {
Log.e(TAG, "inflated=" + inflated + " stub=" + stub);
}
});
if (!binding.viewStub.isInflated()) {
binding.viewStub.getViewStub().inflate();
}
事件处理两种方式
方法引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class MyHandlers {
public void onClickFriend(View view) { ... }
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="handlers" type="com.example.MyHandlers"/>
<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>监听绑定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class Presenter {
public void onSaveClick(Task task){}
}
绑定点击事件到类
<?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>“@{() -> presenter.onSaveClick(task)}”可以忽略所有参数,也可以写上参数
1
android:onClick="@{(view) -> presenter.onSaveClick(task)}"
如果类中有参数View
1
2
3
4
5public class Presenter {
public void onSaveClick(View view, Task task){}
}
绑定点击事件
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"可以使用lambda表达式添加更多参数
1
2
3
4
5
6public class Presenter {
public void onCompletedChanged(Task task, boolean completed){}
}
<CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"
android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />如果类中的方法返回值不是void,点击事件的绑定表达式返回值应该和方法一样,如果返回值因为空对象不能判断,会返回对象的默认值
1
2
3
4public class Presenter {
public boolean onLongClick(View view, Task task){}
}
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"可以使用void作为三元运算符的标记
1
android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
避免复杂监听,有些默认的点击事件处理的实现
1
2
3
4
5SearchView setOnSearchClickListener(View.OnClickListener) android:onSearchClick
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomOut
ZoomControls setOnZoomOutClickListener(View.OnClickListener) android:onZoomOut
Layout Details
data元素标签下,可以导入多个类
1
2
3<data>
<import type="android.view.View">
</data>binding表达式中可以使用View
1
2
3
4
5<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>导入类名冲突可以使用别名
1
2
3<import type="android.view.View"/>
<import type="com.example.real.estate.View"
alias="Vista"/>导入类可以设置变量
1
2
3
4
5
6<data>
<import type="com.example.User"/>
<import type="java.util.List"/>
<variable name="user" type="User"/>
<variable name="userList" type="List<User>"/>
</data>引用静态变量和方法
1
2
3
4
5
6
7
8
9<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"/>如果variable实现了android.databinding.Observable或者observable collection,应该在反映在type中,当没有实现上述接口时,变量将不会被观察Observer
- 当横竖屏不同布局时,变量会整合,不同布局不要有命名冲突
- binding 类的主要方法,其中context是来自rootview的context
binding.getRoot().getContext()
重命名Binding对象
1
2<data class="MainAty">
MainAty binding = DataBindingUtil.setContentView(this, R.layout.activity_main);设置Binding包名
1
2
3
4当前应用包名,这种方式会导致Binding对view id绑定失效
<data class=".MainAty">
重新设置完整的包名
<data class="com.main.MainAty">include 绑定变量
1
2<include layout="@layout/contact"
bind:user="@{user}"/>include布局元素不能作为merge的子元素
表达式语法
1
2
3
4exapmples:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'非空判断??
1
2
3android:text="@{user.displayName ?? user.lastName}"
等价于
android:text="@{user.displayName != null ? user.displayName : user.lastName}"规避空指针异常,当variable为空时,属性值会显示相应的默认值
集合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24\<import type="java.util.List" />
<import type="android.util.SparseArray" />
<import type="java.util.Map" />
<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" />
android:text="@{list[index]}"
android:text="@{sparse[index]}"
android:text="@{map[key]}"字符串引用
1
2
3android:text='@{map["firstName"]}'
android:text="@{map[`firstName`}"
android:text="@{map['firstName']}"表达式中的资源引用
数据对象
数据自动更新通知机制
1
2
3Observable objects
Observable fields
Observable collectionsObservable Objects 继承自BaseObservable
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54public class BaseObservable implements Observable {
private transient PropertyChangeRegistry mCallbacks;
public BaseObservable() {
}
@Override
public void addOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
synchronized (this) {
if (mCallbacks == null) {
mCallbacks = new PropertyChangeRegistry();
}
}
mCallbacks.add(callback);
}
@Override
public void removeOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
synchronized (this) {
if (mCallbacks == null) {
return;
}
}
mCallbacks.remove(callback);
}
/**
* Notifies listeners that all properties of this instance have changed.
*/
public void notifyChange() {
synchronized (this) {
if (mCallbacks == null) {
return;
}
}
mCallbacks.notifyCallbacks(this, 0, null);
}
/**
* Notifies listeners that a specific property has changed. The getter for the property
* that changes should be marked with {@link Bindable} to generate a field in
* <code>BR</code> to be used as <code>fieldId</code>.
*
* @param fieldId The generated BR id for the Bindable field.
*/
public void notifyPropertyChanged(int fieldId) {
synchronized (this) {
if (mCallbacks == null) {
return;
}
}
mCallbacks.notifyCallbacks(this, fieldId, null);
}
}在getter()添加@Bindable注解,在setter()中添加notifyPropertyChanged(BR.lastName);
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
30
31
32
33
34private 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);
}
}
上面user例子,实现自动刷新UI
@Bindable
public boolean isAdult() {
return isAdult;
}
public void setAdult(boolean adult) {
isAdult = adult;
notifyPropertyChanged(BR.adult);
}
user.isAdult = !user.isAdult;
binding.setUser(user);Observable Fields 不需要写
setter() getter()
,需要初始化new Observable***()
1
2public class ObservableParcelable<T extends Parcelable>
extends ObservableField<T> implements Parcelable, Serializable实例
1
2
3
4
5
6
7
8
9
10private 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 Collections
ObservableArrayMap
1
2
3
4
5
6
7ObservableArrayMap<String, Object> observableMap = new ObservableArrayMap<>();
observableMap.put("firstName", "Google");
observableMap.put("lastName", "Inc.");
observableMap.put("age", 17);
binding.setObservableMap(observableMap);
android:text='@{String.valueOf(observableMap["age"])}'ObservableArrayList
1
2
3
4
5
6
7ObservableArrayList<Object> list = new ObservableArrayList<>();
list.add("Google");
list.add("Inc.");
list.add(17);
binding.setObservableList(list);
android:text="@{String.valueOf(observableList[index])}"
Advanced Binding 高级绑定
动态变量
RecyclerView.Adapter中在onBindViewHolder(VH, int)
设置值,其中BindingHolder有一个getBinding()方法1
2
3
4
5public void onBindViewHolder(BindingHolder holder, int position) {
final T item = mItems.get(position);
holder.getBinding().setVariable(BR.item, item);
holder.getBinding().executePendingBindings();
}立即绑定
当变量或观察者发生变化时,绑定会在下一帧绘制时发生改变,然而有时候需要强制重新绑定1
android.databinding.ViewDataBinding.executePendingBindings()
后台线程 可以在后台线程改变数据model,只要不是集合,数据绑定本地化每个变量,规避线程并发问题
属性设置
自动设置属性
布局文件1
2
3
4
5
6<ImageView
android:layout_width="200dp"
android:layout_height="200dp"
android:scaleType="centerCrop"
app:imageUrl="@{user.imageUrl}"
app:placeHolder="@{@drawable/place}" />可以在任意类中绑定属性值设置方法,通常以模块,view自定义命名的Bindings类
1
2
3
4
5
6
7
8@BindingAdapter(value = {"imageUrl", "placeHolder"}, requireAll = false)
public static void setImageUrl(ImageView imageView, String url, Drawable placeHolder) {
if (!TextUtils.isEmpty(url)){
Glide.with(imageView.getContext()).load(url).into(imageView).onLoadStarted(placeHolder);
} else {
imageView.setImageDrawable(placeHolder);
}
}重命名setters,Android 实现了很多BindingAdapters,查看TextViewBinding
1
2
3
4
5
6
7
8
9
10
11
12@BindingMethods({
@BindingMethod(type = TextView.class, attribute = "android:autoLink", method = "setAutoLinkMask"),
@BindingMethod(type = TextView.class, attribute = "android:drawablePadding", method = "setCompoundDrawablePadding"),
@BindingMethod(type = TextView.class, attribute = "android:editorExtras", method = "setInputExtras"),
@BindingMethod(type = TextView.class, attribute = "android:inputType", method = "setRawInputType"),
@BindingMethod(type = TextView.class, attribute = "android:scrollHorizontally", method = "setHorizontallyScrolling"),
@BindingMethod(type = TextView.class, attribute = "android:textAllCaps", method = "setAllCaps"),
@BindingMethod(type = TextView.class, attribute = "android:textColorHighlight", method = "setHighlightColor"),
@BindingMethod(type = TextView.class, attribute = "android:textColorHint", method = "setHintTextColor"),
@BindingMethod(type = TextView.class, attribute = "android:textColorLink", method = "setLinkTextColor"),
@BindingMethod(type = TextView.class, attribute = "android:onEditorAction", method = "setOnEditorActionListener"),
})自定义设置方法
有下面的方法
setPadding(int left, int top, int right, int bottom)
没有setPaddingLeft()
,但是有android:paddingLeft
属性1
2
3
4
5
6
7
8
9@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());
}
}一个抽象方法的接口或抽象类的事件处理,注意,此方法必须在自定义View中编写,下面的是官方文档代码示例
1
2
3
4
5
6
7
8
9
10
11
12@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);
}
}
}如果是多个抽象方法的事件处理
1
2
3
4
5
6
7
8
9@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewDetachedFromWindow {
void onViewDetachedFromWindow(View v);
}
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewAttachedToWindow {
void onViewAttachedToWindow(View v);
}
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44@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实现自定义监听事件处理
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73public class ColorPicker extends View {
private int color;
public ColorPicker(Context context) {
super(context);
}
public ColorPicker(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public ColorPicker(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(getResources().getColor(color));
}
public void removeListener(OnColorChangeListener oldListener) {
oldListener.onColorChange(this, color);
Log.e("color", "remove");
}
public void addListener(OnColorChangeListener newListener) {
newListener.onColorChange(this, color);
Log.e("color", "add");
}
public interface OnColorChangeListener {
void onColorChange(ColorPicker view, int newColor);
}
@BindingAdapter("onColorChange")
public static void setColorChangeListener(ColorPicker view,
ColorPicker.OnColorChangeListener oldListener,
ColorPicker.OnColorChangeListener newListener) {
Log.e("oldListener", "="+oldListener);
Log.e("newListener", "="+newListener);
if (oldListener != null) {
view.removeListener(oldListener);
}
if (newListener != null) {
view.addListener(newListener);
}
}
}
布局引用
<com.willkernel.www.databindingdemo.ColorPicker
android:id="@+id/colorPicker"
android:layout_width="100dp"
android:layout_height="100dp"
app:color="@{color}"
app:onColorChange="@{(v,color)->main.colorChanged(v,color)}" />
MainActivity handler中
public void colorChanged(int color) {
Log.e(TAG, "colorChanged=" + color);
}
binding.setColor(R.color.colorAccent);
转换
对象类型转换,需要使用public static修饰方法
1
2
3
4
5@BindingConversion
public static String convertDateToText(Date date){
DateFormat dateFormat=DateFormat.getDateInstance();
return dateFormat.format(date);
}自定义转换 颜色值int color转换为ColorDrawable,转换发生在setter level,不允许两种类型混合使用,int和drawable在同一个三元运算符中
1
2
3
4
5
6
7
8
9<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}