在没有Databinding之前,布局文件通常只负责UI控件的布局工作,我们是在页面中通过id找到控件,接着在页面中通过代码对控件进行操作。为了减轻页面的工作量,google推出Databinding,让布局文件承担了部分原本属于页面的工作,也使页面与布局文件之间的耦合度进一步降低。
Databinding优势:
- 项目更简洁,可读性更高。部分与UI控件相关的代码可以在布局文件中完成。
- 不再需要findViewById()方法。
- 布局文件可以包含简单的业务逻辑。UI控件能够直接与数据模型中的字段绑定,甚至能响应用户的交互。
DataBinding生成布局管理文件
每个使用了databiding的xml文件经过编译之后,都会通过Apt(annotation-processing-tool)注解处理器生成相应的java文件,文件名为xml文件名加上Binding后缀,这个类会继承于ViewDataBinding。它是该xml文件view的管理类,我们可以通过操作这个文件来对xml文件生成的view进行操作。
比如activity_main.xml会生成对应的ActivityMainBinding.java文件。生成路径为:

抽象类ActivityMainBinding代码:
public abstract class ActivityMainBinding extends ViewDataBinding {
@NonNull
public final TextView myTextview;
protected ActivityMainBinding(Object _bindingComponent, View _root, int _localFieldCount,
TextView myTextview) {
super(_bindingComponent, _root, _localFieldCount);
this.myTextview = myTextview;
}
@NonNull
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
@Nullable ViewGroup root, boolean attachToRoot) {
return inflate(inflater, root, attachToRoot, DataBindingUtil.getDefaultComponent());
}
@NonNull
@Deprecated
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
@Nullable ViewGroup root, boolean attachToRoot, @Nullable Object component) {
return ViewDataBinding.<ActivityMainBinding>inflateInternal(inflater, R.layout.activity_main, root, attachToRoot, component);
}
@NonNull
@Deprecated
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
@Nullable Object component) {
return ViewDataBinding.<ActivityMainBinding>inflateInternal(inflater, R.layout.activity_main, null, false, component);
}
public static ActivityMainBinding bind(@NonNull View view) {
return bind(view, DataBindingUtil.getDefaultComponent());
}
@Deprecated
public static ActivityMainBinding bind(@NonNull View view, @Nullable Object component) {
return (ActivityMainBinding)bind(component, view, R.layout.activity_main);
}
}
如何获取ActivityMainBinding对象
这里需要用到DataBindingUtil类获取ActivityMainBinding
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
DataBindingUtil中相关代码:
public class DataBindingUtil {
private static DataBinderMapper sMapper = new DataBinderMapperImpl();
public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
int layoutId) {
return setContentView(activity, layoutId, sDefaultComponent);
}
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);
}
}
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);
}
}
与activity中设置布局方式一样,内部也是调用了activity.setContentView(layoutId),然后获取activity的decorView,然后获取内层的contentView,调用bindToAddedViews方法,获取添加的子view,然后调用了bind(component, children, layoutId)方法。
@SuppressWarnings("unchecked")
static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View[] roots,
int layoutId) {
return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId);
}
static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,
int layoutId) {
return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
}
该bind方法内部调用了sMapper.getDataBinder方法。
抽象类DataBinderMapper代码:
@RestrictTo(RestrictTo.Scope.LIBRARY)
@SuppressWarnings("WeakerAccess")
public abstract class DataBinderMapper {
public abstract ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View view,
int layoutId);
public abstract ViewDataBinding getDataBinder(DataBindingComponent bindingComponent,
View[] view, int layoutId);
public abstract int getLayoutId(String tag);
public abstract String convertBrIdToString(int id);
@NonNull
public List<DataBinderMapper> collectDependencies() {
// default implementation for backwards compatibility.
return Collections.emptyList();
}
}
getDataBinder方法的具体实现是在DataBinderMapperImpl中,实现代码如下:
@Override
public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
if(localizedLayoutId > 0) {
final Object tag = view.getTag();
if(tag == null) {
throw new RuntimeException("view must have a tag");
}
switch(localizedLayoutId) {
case LAYOUT_ACTIVITYMAIN: {
if ("layout/activity_main_0".equals(tag)) {
return new ActivityMainBindingImpl(component, view);
}
throw new IllegalArgumentException("The tag for activity_main is invalid. Received: " + tag);
}
}
}
return null;
}
判断view文件的tag,然后初始化ActivityMainBindingImpl,查看layout下的activity_main.xml,根布局的tag就是"layout/activity_main_0",所以下一步进入ActivityMainBindingImpl的构造方法:
public ActivityMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
this(bindingComponent, root, mapBindings(bindingComponent, root, 2, sIncludes, sViewsWithIds));
}
private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
super(bindingComponent, root, 0
, (android.widget.TextView) bindings[1]
);
this.mboundView0 = (androidx.constraintlayout.widget.ConstraintLayout) bindings[0];
this.mboundView0.setTag(null);
setRootTag(root);
// listeners
invalidateAll();
}
再调用ViewDataBinding中的mapBindings方法:
protected static Object[] mapBindings(DataBindingComponent bindingComponent, View root,
int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds) {
Object[] bindings = new Object[numBindings];
mapBindings(bindingComponent, root, bindings, includes, viewsWithIds, true);
return bindings;
}
该方法内部用于解析xml文件中的内容返回一个bindings数组,该数组为解析之后的各个view。在xml中声明的view会在解析阶段之后存储在ActivityMainBindingImpl中,那么就能实现通过databinding获取到我们声明的控件了。
网友评论