摘要
在google 刚推出 DataBinding 时是只支持单向绑定的,也即数据可以显示到 View 上来,而 View 上进行更新却不能同步到 数据上去。而现在则可以。
除数据外,在响应事件上也比原来更加方便,快捷。
属性双向绑定示例
效果:
代码: Setting.java
public class Setting extends BaseObservable{
private boolean voiceOn = false;
private boolean vibrateOn = false;
private boolean cacheEnable = false;
private int cacheSize = 0;
@Override
public void notifyPropertyChanged(int fieldId) {
super.notifyPropertyChanged(fieldId);
if (fieldId != BR.description){
notifyPropertyChanged(BR.description);
}
}
@Bindable
public void setCacheEnable(boolean cacheEnable) {
this.cacheEnable = cacheEnable;
notifyPropertyChanged(BR.cacheEnable);
}
@Bindable
public void setCacheSize(int cacheSize) {
this.cacheSize = cacheSize;
notifyPropertyChanged(BR.cacheSize);
}
@Bindable
public void setVibrateOn(boolean vibrateOn) {
this.vibrateOn = vibrateOn;
notifyPropertyChanged(BR.vibrateOn);
}
@Bindable
public void setVoiceOn(boolean voiceOn) {
this.voiceOn = voiceOn;
notifyPropertyChanged(BR.voiceOn);
}
@Bindable
public int getCacheSize() {
return cacheSize;
}
@Bindable
public boolean isCacheEnable() {
return cacheEnable;
}
@Bindable
public boolean isVibrateOn() {
return vibrateOn;
}
@Bindable
public boolean isVoiceOn() {
return voiceOn;
}
@Bindable
public String getDescription(){
return this.toString();
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(this.getClass().getSimpleName());
stringBuilder.append(": ");
stringBuilder.append("\nvibrateOn:");
stringBuilder.append(vibrateOn);
stringBuilder.append("\nvoiceOn:");
stringBuilder.append(voiceOn);
stringBuilder.append("\ncacheEnable: ");
stringBuilder.append(cacheEnable);
if (cacheEnable){
stringBuilder.append("\ncacheSize: ");
stringBuilder.append(cacheSize);
}
return stringBuilder.toString();
}
}
对应的xml 文件: activity_syn.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="android.view.View" />
<variable name="setting" type="me.leo.mvvm.bean.Setting" />
</data>
<GridLayout android:layout_width="match_parent" android:layout_height="match_parent" android:columnCount="2">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/syn_vibrate" />
<ToggleButton android:id="@+id/syn_vibrate_on" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="end" android:checked="@={setting.vibrateOn}" android:background="@drawable/toggle_btn" android:textOff="" android:textOn=""/>
<TextView android:text="@string/syn_voice" android:layout_height="wrap_content" android:layout_width="wrap_content"/>
<ToggleButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="end" android:checked="@={setting.voiceOn}" android:background="@drawable/toggle_btn" android:textOff="" android:textOn=""/>
<TextView android:text="@string/syn_cache" android:layout_height="wrap_content" android:layout_width="wrap_content"/>
<ToggleButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="end" android:checked="@={setting.cacheEnable}" android:background="@drawable/toggle_btn" android:textOff="" android:textOn=""/>
<TextView android:text="@string/syn_cache_size" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="@{setting.cacheEnable ? View.VISIBLE: View.GONE}" android:layout_columnSpan="2"/>
<SeekBar android:layout_height="wrap_content" android:layout_width="match_parent" android:visibility="@{setting.cacheEnable ? View.VISIBLE: View.GONE}" android:progress="@={setting.cacheSize}" android:max="1000" android:layout_columnSpan="2"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/syn_setting" android:layout_columnSpan="2"/>
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{setting.description}" android:layout_columnSpan="2"/>
</GridLayout>
</layout>
首先观察怎么用的,这里 Setting
继承了 BaseObservable
通过调用 notifyPropertyChanged(int fieldId);
来通知 View 更新数据。在需要使用的 field 的对应的 getter 和 setter 上加上注解 @Bindable
从而实现与 xml 的绑定。
而 xml 文件从原来的 @{variable.value}
变成了 @={variable.value}
用以响应界面上对数据的更改。
两相结合,在 activity 中只要将两者按类似下面这样绑定,即可实现 View 实时相应界面其它部分的变化。同时绑定时的 Setting 值也同时已经更新。
public class SynActivity extends AppCompatActivity{
private Setting setting = new Setting();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivitySynBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_syn);
binding.setSetting(setting);
}
}
还有另外一种方式实现绑定,即使用 ObservableField<T>
这样的方式,可以有类似下面这样的代码:
public class Setting extends BaseObservable{
public final ObservableField<String> description = new ObservableField<>();
public final ObservableBoolean voiceOn = new ObservableBoolean();
}
查看 ObservableField 的定义
public class ObservableField<T> extends BaseObservable implements Serializable public class BaseObservable implements Observable
可以发现还是上面的那一套。
那么下面的这一套要响应事件也就可以通过 BaseObservable
的
public void addOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
synchronized (this) {
if (mCallbacks == null) {
mCallbacks = new PropertyChangeRegistry();
}
}
mCallbacks.add(callback);
}
将需要处理的事件添加进去即可。如上面的这种即可在构造函数中对需要关注的 Field 添加 callback ,当其值改变的时候,通知 description 值也发生了变化,需要注意的是,通过这种方式使用的 Field 必须要声明为 public, 原因应该比较容易想到。
事件绑定
在未使用 databinding 的情况下 xml 也是支持部分事件绑定的,如:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" tools:context=".MainActivity">
<Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/to_list" android:onClick="toList"/>
</LinearLayout>
然后在对应的 MainActivity 中加上这样的一个函数
public void toList(View view){
// 你要做的事情
}
在单击这个 Button 的时候就会调用这样的一个函数。但这样的操作是有限制的, 首先,我们需要声明 tools名称空间,然后设定 Context 是 MainActivity。其次,定义在 MainActivity 中的函数形式必须与 OnClickListener 的参数形式完全一致,否则不可调用。
而 DataBinding 则不一定要这样。
DataBinding 的事件处理有两种方式:
- 绑定 Method,如同上面的那样,其函数入参必须要和原来的一致
- 绑定 Listener,没有Method 这样的要求,这里会使用一个 Lambda 表达式进行实现。
以上两者另外的一个区别在于事件绑定时间, Method 在数据设定时绑定,而 Listener 在事件触发的时候
两个简单的例子,来源于 谷歌 DataBinding 介绍
首先看 Method Binding
public class MyHandlers {
public void onClickFriend(View view) { ... }
}
对应的 xml
<?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>
而对应的 Listener Bindings 则如下
public class Presenter {
public void onSaveClick(Task task){}
}
对应的 xml
<?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>
这里的例子中 Method Binding 在单击的时候,直接调用了 Handler 的 OnClickFriend 方法,传参为 andriod.view.View 而 Listener Binding 对应的则是通过 Lambda 封装了一层,在封装的那一层里面通过 presenter 调用了对应的 task 任务。
而且 Listener Binding 可以改造成这样的:
public class Presenter {
public void onSaveClick(View view, Task task){}
}
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}" />
甚至对于 CheckBox 这样的还有:
public 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)}" />
注意:
ListenerBinding 和 Method Binding 的返回类型必须要和 View 对应的事件类型所需要的返回值一致
小结
以上是关于数据双向绑定主要需要了解的一些内容,具体细节还有很多:
- 在 xml 中进行数据操作,可以进行的运算
- 设定自定义 View 时需要使用的方法
- 自定义属性的方法
- …
但不适合记录在这里了,数据绑定内容基本到此结束,但如何真正实现一个 mvvm 的应用,这还是一个问题,后面继续探究。
参考资料
本篇文章参考资料完全来源于 Google