DataBinding結(jié)合BaseActivity和多狀態(tài)布局使用的踩坑記

轉(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);
    }
WTF

如此順利就找到了問(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容