Data Binding入门(Kotlin)
官方文档
https://developer.android.google.cn/topic/libraries/data-binding/
Demo传送门
在gradle中添加databinding 为了适配kotlin还要加插件
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-android'
android {
dataBinding {
enabled = true
}
}
onCreate中绑定布局
// The layout for this activity is a Data Binding layout so it needs to be inflated using
// DataBindingUtil.
val binding: ActivityMainBinding = DataBindingUtil.setContentView(
this, R.layout.activity_main)
// The returned binding has references to all the Views with an ID.
binding.observableFieldsActivityButton.setOnClickListener {
startActivity(Intent(this, ObservableFieldActivity::class.java))
}
binding.viewmodelActivityButton.setOnClickListener {
startActivity(Intent(this, ViewModelActivity::class.java))
}
设置数据
//.user是在xml布局中设置的data
<data>
<variable name="user" type="com.example.admin.myapplication.User"/>
<variable name="presenter" type="com.example.admin.myapplication.DataActivity.Presenter"/>
</data>
val user = User("aaaa", "bbb")
dataBinding.user=User("aaaa","bbb")
dataBinding.setVariable(BR.user,user)
problem
在xml中写的语法如果有错误的话没有明确提示,只会编译不过
在xml中设置了引用,在activity中再setText无效
android:text="@{user.name}"
dataBinding.aaaa.text = User("aaa","bbb").firstName
dataBinding.aaaa.setText(User("aaa","bbb").firstName)
BR
binding resourse(R文件)
源码分析
调用DataBindingUtil.setContentView之后会调用调用DataBindingUtil的setContentView方法。在这里面getDecorView获取顶层视图,再获取整个viewGroup,把它绑定在一起。
// @Nullable don't annotate with Nullable. It is unlikely to be null and makes using it from
// kotlin really ugly. We cannot make it NonNull w/o breaking backward compatibility.
public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
int layoutId, @Nullable DataBindingComponent bindingComponent) {
activity.setContentView(layoutId);
View decorView = activity.getWindow().getDecorView();
ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content);
return bindToAddedViews(bindingComponent, contentView, 0, layoutId);
}
然后会回到ActivityDataBinding的bind方法中,返回ActivityDataBinding的实例。
@NonNull
public static ActivityDataBinding bind(@NonNull android.view.View view, @Nullable android.databinding.DataBindingComponent bindingComponent) {
if (!"layout/activity_data_0".equals(view.getTag())) {
throw new RuntimeException("view tag isn't correct on view:" + view.getTag());
}
return new ActivityDataBinding(bindingComponent, view);
}
在ActivityDataBinding的构造方法中mapBindings里面会去遍历里面所有的view,并添加到一个集合里面返回回来,然后就拿到了所有的view,可以看到this.edittext等设置
public ActivityDataBinding(@NonNull android.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
super(bindingComponent, root, 0);
final Object[] bindings = mapBindings(bindingComponent, root, 5, sIncludes, sViewsWithIds);
this.aaaa = (android.widget.TextView) bindings[1];
this.aaaa.setTag(null);
this.bbbb = (android.widget.TextView) bindings[2];
this.bbbb.setTag(null);
this.edittext = (android.widget.EditText) bindings[3];
this.edittext.setTag(null);
this.followEdittext = (android.widget.TextView) bindings[4];
this.mboundView0 = (android.widget.LinearLayout) bindings[0];
this.mboundView0.setTag(null);
setRootTag(root);
// listeners
mCallback1 = new android.databinding.generated.callback.OnClickListener(this, 1);
invalidateAll();
}
这个时候已经拿到了所有view的对象,现在就可以对他们赋值了。赋值在setVariable方法里面。
//这两个方法其实是一样的, .user也会调用.setVariable方法
dataBinding.user=User("aaaa","bbb")
dataBinding.setVariable(BR.user,user)
在setVariable里面有在xml中设置的所有data
@Override
public boolean setVariable(int variableId, @Nullable Object variable) {
boolean variableSet = true;
if (BR.presenter == variableId) {
setPresenter((com.example.admin.myapplication.DataActivity.Presenter) variable);
}
else if (BR.user == variableId) {
setUser((com.example.admin.myapplication.User) variable);
}
else {
variableSet = false;
}
return variableSet;
}
在setUser中会有一个通知notifyPropertyChanged
public void setUser(@Nullable com.example.admin.myapplication.User User) {
this.mUser = User;
synchronized(this) {
mDirtyFlags |= 0x2L;
}
notifyPropertyChanged(BR.user);
super.requestRebind();
}
最后执行的操作是在executeBindings中进行的,可以看到里面有setText的操作,那么基础操作流程就结束了
@Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
android.view.View.OnClickListener presenterOnClickAndroidViewViewOnClickListener = null;
com.example.admin.myapplication.DataActivity.Presenter presenter = mPresenter;
java.lang.String userFirstName = null;
android.databinding.adapters.TextViewBindingAdapter.OnTextChanged presenterOnTextChangedAndroidDatabindingAdaptersTextViewBindingAdapterOnTextChanged = null;
com.example.admin.myapplication.User user = mUser;
java.lang.String userLastName = null;
if ((dirtyFlags & 0x5L) != 0) {
if (presenter != null) {
// read presenter::onClick
presenterOnClickAndroidViewViewOnClickListener = (((mPresenterOnClickAndroidViewViewOnClickListener == null) ? (mPresenterOnClickAndroidViewViewOnClickListener = new OnClickListenerImpl()) : mPresenterOnClickAndroidViewViewOnClickListener).setValue(presenter));
// read presenter::onTextChanged
presenterOnTextChangedAndroidDatabindingAdaptersTextViewBindingAdapterOnTextChanged = (((mPresenterOnTextChangedAndroidDatabindingAdaptersTextViewBindingAdapterOnTextChanged == null) ? (mPresenterOnTextChangedAndroidDatabindingAdaptersTextViewBindingAdapterOnTextChanged = new OnTextChangedImpl()) : mPresenterOnTextChangedAndroidDatabindingAdaptersTextViewBindingAdapterOnTextChanged).setValue(presenter));
}
}
if ((dirtyFlags & 0x6L) != 0) {
if (user != null) {
// read user.firstName
userFirstName = user.getFirstName();
// read user.lastName
userLastName = user.getLastName();
}
}
// batch finished
if ((dirtyFlags & 0x5L) != 0) {
// api target 1
this.aaaa.setOnClickListener(presenterOnClickAndroidViewViewOnClickListener);
android.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.edittext, (android.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (android.databinding.adapters.TextViewBindingAdapter.OnTextChanged)presenterOnTextChangedAndroidDatabindingAdaptersTextViewBindingAdapterOnTextChanged, (android.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, (android.databinding.InverseBindingListener)null);
}
if ((dirtyFlags & 0x6L) != 0) {
// api target 1
android.databinding.adapters.TextViewBindingAdapter.setText(this.aaaa, userFirstName);
android.databinding.adapters.TextViewBindingAdapter.setText(this.bbbb, userLastName);
}
if ((dirtyFlags & 0x4L) != 0) {
// api target 1
this.bbbb.setOnClickListener(mCallback1);
}
}
处理layout文件:要移除掉layout和data标签,还原成之前
解析表达式: android:text="@{user.name}"
依赖解析:解析表达式里面的是参数还是方法等等
找到setter:依赖解析完之后运算里面的表达式
apt代码生成,里面的方法基本都是static
标记位:executeBindings方法中的dirtyFlags。每个操作都会有一个标记位
批量操作:修改了user的数据不会立即刷新到view中,只有调用了setVariable才会改变
缓存表达式:相同的表达式不会重复运算,直接拿值
Data Binding会自动检测对象是否为空。源码中也能看到,在DataBindingUtil的executeBindings方法中有对user进行非空判断。
Data Binding不会对数组索引进行检测,要自己排除越界问题
还可以bind多个,只要你include里面也写了对应的data和layout标签
<include layout="@layout/include_demo" bind:user="@{user}" bind:presenter="@{presenter}"/>
BaseObservable
数据类继承BaseObservable,get方法设置@Bindable注解,然后在set方法里面通知内容改变notifyPropertyChanged,这样就可以在不调用setVariable的时候也能刷新数据了。
public class User extends BaseObservable{
@Bindable
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String name){
firstName=name;
notifyPropertyChanged(BR.firstName);
}
如果不用Bindable注解,可以在set方法里面调用notifyChange();这样会全体刷新
public void setLastName(String name){
lastName=name;
notifyChange();
}
Observable Fields
观察成员变量
public ObservableBoolean isShow;
Observable Collection
设置集合,在xml布局中可以直接使用
public ObservableArrayMap<String,String> map = new ObservableArrayMap<>();
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
map.put("aaa","asdf");
map.put("vd","sdf");
map.put("dfga","fg");
}
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{user.map["dfga"]}'
/>
想在xml中使用view的属性要导包
<data>
<import type="android.view.View"/>
</data>
android:visibility="@{user.isShow?View.GONE:View.VISIBLE}"