MVVM之 mViewStubContent.setId(android.R.id.content);

286 阅读1分钟

在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,就会引起报错