dataBinding是Google退出的数据绑定支持库,使用该库可以直接实现数据Model和页面的双向绑定。
实现方法
1、在app下的build.gradle的android{…}中添加:
dataBinding {
enabled = true
}
2、新建bean对象:
public class UserInfo{
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
3、在layout的xml文件中添加数据源:
格式是:
如:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="com.example.mvvp2demo.bean.UserInfo" />
</data>
<LinearLayout
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
tools:context=".MainActivity">
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.username}"/>
<EditText
android:layout_marginTop="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.password}"/>
</LinearLayout>
</layout>
其中的data标签用来标识model,type不是类型,而是bean类的路径。将EditText的text属性设置成@{user.xx},就可以获取自定义bean对象的属性。
4、rebuild一下,apt工具会自动在build文件夹下生成多个文件
5、在MainActivity.xml中通过DataBindingUtil工具类设置布局文件,并来获取binding对象,bingding类在刚刚rebuild的时候已自动生成,它的类名是:布局名+Binding,如布局文件名是:activity_main.xml,那么生成的类名是ActivityMainBinding.
使用DataBindingUtil.setContentView方法替换原本的setContentView方法,并获取它返回的对象:
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
通过类对象的set方法,就可以设置自定义的bean对象了。
binding.setUser(info);
完整的MainActivity.java:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
UserInfo info = new UserInfo();
info.setUsername("username");
info.setPassword("password");
binding.setUser(info);
}
}
此时数据已经能显示到控件上了:
试试看Model层的数据改变时控件的数据是否会改变,设置一个定时器:
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
info.setUsername("username2");
info.setPassword("password2");
}
}, 5000);
5秒过后,什么也没有发生!
这是因为此时的数据还没有设置监听,需要对Bean类的属性做一些额外的设置:
import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;
import androidx.databinding.library.baseAdapters.BR;
public class UserInfo extends BaseObservable {
private String username;
private String password;
@Bindable
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
notifyPropertyChanged(BR.username);
}
@Bindable
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
notifyPropertyChanged(BR.password);
}
}
继承BaseObservable,给set方法设置notifyPropertyChanged(BR.password);给get方法设置注解,然后运行。
这时发现model层的值变了,5秒后控件的数据值改变了,同时控件收到了这个变化,于是控件的值也改变了。这就是单向绑定:Model层→View层。
还有一种方式,直接将bean改成这样:
import androidx.databinding.BaseObservable;
import androidx.databinding.ObservableField;
public class UserInfo extends BaseObservable {
public ObservableField<String> username = new ObservableField<>();
public ObservableField<String> password = new ObservableField<>();
}
这样的效果与上一种相同,只是属性值的获取稍微发生了变化:
MainActivity.java:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
final UserInfo info = new UserInfo();
info.username.set("username");
info.password.set("password");
binding.setUser(info);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
info.username.set("username2");
info.password.set("password2");
}
}, 5000);
}
}
5秒过后View层的控件值变了,效果与之前的一样!但是这样,View层控件值改变,Model层的数据能监听到吗?
试试这样:
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
usernameEt.setText("123456789");
passwordEt.setText("abcdefg");
Log.d(TAG, "run: " + "username:"+info.getUsername()+",password:"+info.getPassword());
}
}, 5000);
设置一个定时器,5秒后控件的值发生变化,然后打印Model层的数据值:
可见Model层的数据值并没有随着控件的值发生改变。
这时其实只要在之前View层的控件值中,这样修改:
<EditText
android:id="@+id/et_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.username}"/>
<EditText
android:id="@+id/et_pwd"
android:layout_marginTop="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.password}"/>
改为:
<EditText
android:id="@+id/et_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@={user.username}"/>
<EditText
android:id="@+id/et_pwd"
android:layout_marginTop="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@={user.password}"/>
这样,就完成了View层→Model层的绑定,双向绑定就完成了!
原理分析
其实原理分析就是看看自动生成的那些文件里都做了什么。
首先从DataBindingUtil的setContentView方法进入:
继续进入:
可以看出,这个方法把我们传递的activity和layoutId在这里设置了,这就是为啥在MainActivity中可以去掉setContentView的原因。然后获取了应用应用的根布局,传入了这个bindToAddedViews方法。
可以看出这个方法调用了bind方法:
发现return了sMapper的getDataBinder方法。进入getDataBinder方法就会发现这是个抽象方法,
在此之前有个重要的xml文件得看,它的路径是app/build/intermediates/incremental/mergeDebugResources/stripped.dir/layout/activity_main.xml
可以发现它和之前的xml文件布局一模一样,工具会把原xml文件拆分成2个xml,一个是被附上tag交由Android OS渲染,并且会附上tag方便快速找到控件,另一个这是在DataBinding处理。
看看它的子类DataBinderMapperImpl实现,路径在app/build/generated/ap_generated_sources/debug/out/包名/下的就能找到:
其中做了判断,就是把打上tag的view作为参数传给ActivityMainBindingImpl做处理,继续看看ActivityMainBindingImpl,路径在
app/build/generated/ap_generated_sources/debug/out/包名/databinding/ActivityMainBindingImpl
:
其中mapBindings方法较长,大致意思就是把打上tag的控件存入到一个bindings的数组中,
然后把他们一一赋值成mboundView0、mboundView1…
可以看看他们的初始化:
与布局文件中控件也是一一对应的。
然后调用invalidateAll方法,invalidateAll方法调用requestRebind方法:
requestRebind方法:
一开始是一些判断返回之类的信息,然后就是较重要的mUIThreadHandler.post(mRebindRunnable),
看看mRebindRunnable:
可以看到这里添加了一个监听器:ROOT_REATTACHED_LISTENER,定位后看看:
它定义在static中,并且使用binding.mRebindRunnable重新回到之前的实现中。
再来看executePendingBindings方法:
调用的executeBindingsInternal方法,大多是一些判断监听的实现:
直接进入executeBindings方法:
这是个抽象方法,它的实现类是ActivityMainBindingImpl:
@Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
com.example.mvvp2demo.bean.UserInfo user = mUser;
java.lang.String userUsername = null;
java.lang.String userPassword = null;
if ((dirtyFlags & 0xfL) != 0) {
if ((dirtyFlags & 0xbL) != 0) {
if (user != null) {
// read user.username
userUsername = user.getUsername();
}
}
if ((dirtyFlags & 0xdL) != 0) {
if (user != null) {
// read user.password
userPassword = user.getPassword();
}
}
}
// batch finished
if ((dirtyFlags & 0xbL) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView1, userUsername);
}
if ((dirtyFlags & 0x8L) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.mboundView1, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, mboundView1androidTextAttrChanged);
androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.mboundView2, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, mboundView2androidTextAttrChanged);
}
if ((dirtyFlags & 0xdL) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView2, userPassword);
}
}
这里发现mUser就是通过setUser赋值而来的,并且有:
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView1, userUsername);
androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.mboundView2, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, mboundView2androidTextAttrChanged);
androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.mboundView1, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, mboundView1androidTextAttrChanged);
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView2, userPassword);
这样4个方法,看看他们的实现:
当model层的数据改变时就会调用这个方法,并且比较和之前的值后也是通过简单的view.setText(text)改变值,
给view设置监听器:
当控件的值发生变化时,就会调用textAttrChanged.onChange(),onChange()实现:
然后Model层的user对象通过user.setUsername(((java.lang.String) (callbackArg_0)))完成Model层数据的赋值。
Model层变化,view层的数据也变化
为什么Bean类中使用ObservableField和notifyPropertyChanged+@Bindable都能实现类的监听呢?进入notifyPropertyChanged方法:
和看看ObservableField类的set方法:
本质上都是一样的。
我们进去看看它是怎么实现的:
它调用了一个叫notifyRecurse的方法:
进入notifyCallbacks方法(notifyRemainder自己进入看看就知道了,最后又调回来了…):
可以发现调用了
mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2);
这个mNotifier又是什么呢?它是通过构造方法赋值的:
它的子类PropertyChangeRegistry的构造方法传入了这个对象:
这是一个实现了的监听器,监听器中完成了:
@Override
public void onNotifyCallback(Observable.OnPropertyChangedCallback callback, Observable sender,int arg, Void notUsed) {
callback.onPropertyChanged(sender, arg);
}
实现监听器的onPropertyChanged方法。
梳理一下逻辑就是:notifyCallbacks调用
mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2);
mNotifier.onNotifyCallback的实现调用:
callback.onPropertyChanged(sender, arg);
总结就是执行:mCallbacks.get(i).onPropertyChanged(),这个mCallbacks.get(i)获得的对象就是在BaseObservable中的addOnPropertyChangedCallback添加进来的:
addOnPropertyChangedCallback是在ViewDataBinding的addListener方法执行的。
根据这个this可以找到onPropertyChanged的实现:
查看handleFieldChange的实现:
onFieldChange是个抽象方法,谁实现了它?ActivityMainBindingImpl实现了它:
当notifyPropertyChanged被执行时,mDirtyFlags就会被赋于不同的值,executeBindings方法被调用时根据mDirtyFlags不同的值为不同的控件设置值。
总结
所谓的双向绑定,其实无非是双向监听而已,只不过将他封装成了一个工具,甚至还有不少缺点(比如数据层大时,占用内存也很大)