在MVVM中,在BaseActivity中的setContentView为什么要有以下代码:
private void initContentView(@LayoutRes int layoutResID) {
View view = LayoutInflater.from(this).inflate(layoutResID, mViewStubContent, false);
mViewStubContent.setId(android.R.id.content);
mContentView.setId(View.NO_ID);
mViewStubContent.removeAllViews();
mViewStubContent.addView(view);
}
首先我们看下去掉这两行代码会发生什么?
mViewStubContent.setId(android.R.id.content);
mContentView.setId(View.NO_ID);
显然,报错与databinding有关:
原因:
DataBinderMapperImpl.java
@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");
}
}
那么为什么没有Tag呢?tag是什么时候生成的?
我们来看下使用databinding时,带有标签的layout.xml发生了什么?
activity_main.xml
<layout>
<data>
<variable
name="viewModel"
type="com.citybank.test.main.mvvm.viewmodel.MainViewModel" />
</data>
<LinearLayout>
<TextView
android:textAppearance="@style/Text_color_333333_sp_14_bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.tabs[0]}" />
</LinearLayout>
</layout>
编译时自动生成另一个xml,其中带有tag
\build\intermediates\incremental\mergeDebugResources\stripped.dir\layout\activity_main.xml
<LinearLayout android:tag="layout/activity_main_0">
<TextView
android:textAppearance="@style/Text_color_333333_sp_14_bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tag="binding_1" />
</LinearLayout>
因此我们基本可以判定上述错误是由于activity_root1.xml没有<layout标签所导致的,但是activity_root1是通用的布局,各个界面需要自己的databinding,所以不能在activity_root1直接加上<layout,那要怎么解决呢?
我们看下getDataBinder中的view.getTag();这个view是哪里来的,能否把它换成我们每个界面的带有的布局对应的view,这样就有tag了
一切都是从这段代码开始的:
mBinding = DataBindingUtil.setContentView(this, onBindLayout());
在DatabindingUtil.java中我们看到contentView传递给 bindToAddedViews();
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);
}
bindToAddedViews中又将contentView的childView传递给bind();
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);
}
}
bind()中继续将childView传递给getDataBinder
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);
}
至此我们知道这个view.getTag的view是 contentView 的childview,
contentView怎么来的呢?
ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content)
也就是说如果 mViewStubContent.setId(android.R.id.content)的话,contentView就是mViewStubContent,childview就会变成我们要展示的有tag的view; 否则mViewStubContent就是contentView的childview,就会引起报错