定义
数据绑定,是Google官方发布的一个框架,是mvvm在android上的一种实现,用于降低布局和逻辑的耦合性,使代码逻辑更加清晰,可以直接绑定数据到xml中,并实现自动刷新。databinding能够省去findviewbyId,大量减少activity的代码,数据能够单向或双向绑定到layout文件中,有助于防止内存泄漏,而且能自动进行空检测以避免空指针异常。
用法
创建一个User类
import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;
public class User extends BaseObservable {
private String name;
private String password;
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
@Bindable
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
notifyPropertyChanged(BR.password);
}
}
修改需要和User类绑定的布局(
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".MainActivity">
<!-- 引入需要绑定的对象-->
<data>
<variable
name="user"
type="com.example.demo.User" />
</data>
<!-- 原来的布局-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- 通过@{}来引用对象中的数据,@{}是单向绑定、@={}是双向绑定-->
<TextView
android:text="@={user.name}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/tv_name"/>
<TextView
android:text="@={user.password}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/tv_password"/>
</LinearLayout>
</layout>
在activity的oncreate做user和布局文件的绑定设置
public class MainActivity extends AppCompatActivity {
private User user;
private ActivityMainBinding activityMainBinding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 不需要用原来的setContentView函数了
// setContentView(R.layout.activity_main);
user = new User();
user.setName("coffee");
user.setPassword("123");
activityMainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main);
activityMainBinding.setUser(user);
// 用多线程演示效果
new Thread(){
@Override
public void run() {
int i = 0;
while (++i < 10) {
super.run();
user.setPassword(user.getPassword() + "1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
原理解析
DataBindingUtil.setContentView(this,R.layout.activity_main)
public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
int layoutId) {
return setContentView(activity, layoutId, sDefaultComponent);
}
跟踪setContentView(activity, layoutId, sDefaultComponent)
public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
int layoutId, @Nullable DataBindingComponent bindingComponent) {
//还是需要先调用原来activity的setContentView来设置布局文件
activity.setContentView(layoutId);
View decorView = activity.getWindow().getDecorView();
ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content);
return bindToAddedViews(bindingComponent, contentView, 0, layoutId);
}
跟踪bindToAddedViews(bindingComponent, contentView, 0, layoutId)
private static <T extends ViewDataBinding> T bindToAddedViews(DataBindingComponent component,
ViewGroup parent, int startChildren, int layoutId) {
final int endChildren = parent.getChildCount();
final int childrenAdded = endChildren - startChildren;
if (childrenAdded == 1) {
final View childView = parent.getChildAt(endChildren - 1);
return bind(component, childView, layoutId);
} else {
final View[] children = new View[childrenAdded];
for (int i = 0; i < childrenAdded; i++) {
children[i] = parent.getChildAt(i + startChildren);
}
return bind(component, children, layoutId);
}
}
如果有多个子view的🌹,会把子view都放在一个view数组里面
继续跟踪bind(component, children, layoutId)
static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View[] roots,
int layoutId) {
return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId);
}
sMapper是一个抽象类,跟踪一下sMapper
private static DataBinderMapper sMapper = new DataBinderMapperImpl();
由此可知sMapper是DataBinderMapperImpl
继续跟踪sMapper.getDataBinder(bindingComponent, roots, layoutId)
还没写完。。。