轉(zhuǎn)載請(qǐng)注明出處 : http://www.lxweimin.com/p/be740a2eab3a
一般來(lái)說(shuō),大部分項(xiàng)目里都會(huì)用到多狀態(tài)布局,可以很方便的切換讀取中,錯(cuò)誤,空內(nèi)容等界面,而且有時(shí)候我們會(huì)直接把多狀態(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);
}
}
這樣寫法有一些好處,相當(dāng)于給每個(gè)activity根布局外面都包了一層多狀態(tài)布局,不用再寫多狀態(tài)布局的xml布局和代碼,直接調(diào)用父類的即可,代碼侵入性相當(dāng)小。
但是如果項(xiàng)目中用了DataBinding就不一樣了,當(dāng)然我們像往常一樣在Activity里寫DataBindingUtil.setContentView(this, R.layout.activity_main);
時(shí),果然發(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)
很好,那我們一起來(lái)裝個(gè)B,看下ActivityMainBinding的源碼,看看有沒(méi)有辦法兩全其美的解決這個(gè)問(wèn)題,首先定位到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);
}
如此順利就找到了問(wèn)題所在?不過(guò),只看這里,好像依然不知道怎么解決,比如第一個(gè)參數(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);
}
到這來(lái)說(shuō)還是很簡(jiǎn)單的,先調(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方法中。
一般來(lái)說(shuō),contentView的子view只有一個(gè),就是setContentView時(shí)傳遞進(jìn)來(lái)的xml中的根布局,如果調(diào)用addContentView就應(yīng)該會(huì)有多個(gè)了,這里暫時(shí)只分析一個(gè)的情況。
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方法中第一個(gè)參數(shù)view就是setContentView時(shí)xml中的根布局view。
那一開始的異常是怎么回事呢?其實(shí)很簡(jiǎn)單啦,因?yàn)榇藭r(shí)contentView的子view根本不是setContentView時(shí)傳遞進(jìn)來(lái)xml中的根布局view,而是我們寫在BaseActivity里的MultipleStatusLayout。
知道了問(wèn)題,解決起來(lái)當(dāng)然就簡(jiǎn)單了,我們可以把MultipleStatusLayout的id設(shè)置為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);
}
}
當(dāng)然,別忘了在DataBindingUtil.setContentView(this, R.layout.activity_main)
后再把id改回去,不過(guò)即使不改回去,好像也是沒(méi)什么問(wèn)題的。
ok,完結(jié)撒花~~
ps:可能有同學(xué)會(huì)問(wèn),這個(gè)tag到底是怎么回事,為什么xml的根布局view會(huì)有一個(gè)tag,自己分明沒(méi)有寫啊,DataBinding的源碼里也沒(méi)看到哪里有setTag?其實(shí)要講這個(gè)tag就要講DataBinding的實(shí)現(xiàn)原理了,推薦感興趣的同學(xué)看下這篇文章,講的很詳細(xì):http://www.lxweimin.com/p/de4d50b88437