轉(zhuǎn)載請注明出處 : http://www.reibang.com/p/be740a2eab3a
一般來說诫硕,大部分項目里都會用到多狀態(tài)布局,可以很方便的切換讀取中刊侯,錯誤章办,空內(nèi)容等界面,而且有時候我們會直接把多狀態(tài)布局寫到BaseActivity的根布局里滨彻,比如:
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<xxx.xxx.xxx.widget.MultipleStatusLayout
android:id="@+id/multiple_status_layout"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</merge>
在BaseActivity的onCreate中:
protected ViewGroup mContentView;
protected MultipleStatusLayout mMultipleStatusLayout;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.setContentView(R.layout.activity_base_core);
mContentView = (ViewGroup) findViewById(android.R.id.content);
mMultipleStatusLayout = (MultipleStatusLayout) findViewById(R.id.multiple_status_layout);
}
然后再重寫一下setContentView方法:
@Override
public void setContentView(@LayoutRes int layoutResID) {
if (mMultipleStatusLayout != null) {
View view = LayoutInflater.from(this).inflate(layoutResID, mMultipleStatusLayout, false);
mMultipleStatusLayout.removeAllViews();
mMultipleStatusLayout.addView(view);
}
}
這樣寫法有一些好處藕届,相當于給每個activity根布局外面都包了一層多狀態(tài)布局,不用再寫多狀態(tài)布局的xml布局和代碼亭饵,直接調(diào)用父類的即可翰舌,代碼侵入性相當小。
但是如果項目中用了DataBinding就不一樣了冬骚,當然我們像往常一樣在Activity里寫DataBindingUtil.setContentView(this, R.layout.activity_main);
時椅贱,果然發(fā)生了這樣的異常:
Caused by: java.lang.RuntimeException: view tag isn't correct on view:null
at com.xxx.xxx.databinding.ActivityMainBinding.bind(ActivityMainBinding.java:98)
at android.databinding.DataBinderMapper.getDataBinder(DataBinderMapper.java:11)
at android.databinding.DataBindingUtil.bind(DataBindingUtil.java:185)
at android.databinding.DataBindingUtil.bindToAddedViews(DataBindingUtil.java:299)
at android.databinding.DataBindingUtil.setContentView(DataBindingUtil.java:279)
at android.databinding.DataBindingUtil.setContentView(DataBindingUtil.java:261)
很好,那我們一起來裝個B只冻,看下ActivityMainBinding的源碼庇麦,看看有沒有辦法兩全其美的解決這個問題,首先定位到bind方法中:
public static ActivityMainBinding bind(android.view.View view, android.databinding.DataBindingComponent bindingComponent) {
if (!"layout/activity_main".equals(view.getTag())) {
throw new RuntimeException("view tag isn't correct on view:" + view.getTag());
}
return new ActivityMain2Binding(bindingComponent, view);
}
如此順利就找到了問題所在喜德?不過山橄,只看這里,好像依然不知道怎么解決舍悯,比如第一個參數(shù)view是什么航棱?
那么我們就從異常堆棧信息的棧底開始看:
public static <T extends ViewDataBinding> T setContentView(Activity activity, int layoutId) {
return setContentView(activity, layoutId, sDefaultComponent);
}
public static <T extends ViewDataBinding> T setContentView(Activity activity, int layoutId,
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);
}
到這來說還是很簡單的,先調(diào)用了activity.setContentView萌衬,之后findViewById拿到contentView傳遞到bindToAddedViews饮醇。
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);
}
}
bindToAddedViews中主要邏輯是找出contentView的所有子view,并再一步傳遞到bind方法中秕豫。
一般來說朴艰,contentView的子view只有一個,就是setContentView時傳遞進來的xml中的根布局混移,如果調(diào)用addContentView就應該會有多個了祠墅,這里暫時只分析一個的情況。
static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,
int layoutId) {
return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
}
public android.databinding.ViewDataBinding getDataBinder(android.databinding.DataBindingComponent bindingComponent, android.view.View view, int layoutId) {
switch(layoutId) {
case com.xxx.xxx.R.layout.activity_main:
return com.xxx.xxx.databinding.ActivityMainBinding.bind(view, bindingComponent);
}
return null;
}
突然之間真相大白歌径,我們搞清楚了bind方法中第一個參數(shù)view就是setContentView時xml中的根布局view毁嗦。
那一開始的異常是怎么回事呢?其實很簡單啦回铛,因為此時contentView的子view根本不是setContentView時傳遞進來xml中的根布局view狗准,而是我們寫在BaseActivity里的MultipleStatusLayout芯急。
知道了問題,解決起來當然就簡單了驶俊,我們可以把MultipleStatusLayout的id設置為android.R.id.content娶耍,這樣DataBindingUtil的setContentView方法得到的contentView就是MultipleStatusLayout,它的子view自然就是xml中的根布局view饼酿。
我們可以把這一句加在BaseActivity的setContentView中榕酒,改造后的代碼如下:
@Override
public void setContentView(@LayoutRes int layoutResID) {
if (mMultipleStatusLayout != null) {
View view = LayoutInflater.from(this).inflate(layoutResID, mMultipleStatusLayout, false);
mMultipleStatusLayout.setId(android.R.id.content);
mContentView.setId(View.NO_ID);
mMultipleStatusLayout.removeAllViews();
mMultipleStatusLayout.addView(view);
}
}
當然,別忘了在DataBindingUtil.setContentView(this, R.layout.activity_main)
后再把id改回去故俐,不過即使不改回去想鹰,好像也是沒什么問題的。
ok药版,完結(jié)撒花~~
ps:可能有同學會問辑舷,這個tag到底是怎么回事,為什么xml的根布局view會有一個tag槽片,自己分明沒有寫啊何缓,DataBinding的源碼里也沒看到哪里有setTag?其實要講這個tag就要講DataBinding的實現(xiàn)原理了还栓,推薦感興趣的同學看下這篇文章碌廓,講的很詳細:http://www.reibang.com/p/de4d50b88437