1. 程式人生 > >Android開發之setContentView的那些事

Android開發之setContentView的那些事

原文:

setContentView方法位於Window類,實現Window的子類PhoneWindow。
每一個Activity都有一個PhoneWindow

以下為setContentView在PhoneWindow上的實現:

public class PhoneWindow extends Window implements MenuBuilder.Callback {
    //...
    //...
    //...
    //過載方法1
    @Override
    public void setContentView(int layoutResID) {
        if
(mContentParent == null) { installDecor(); } else { mContentParent.removeAllViews(); } mLayoutInflater.inflate(layoutResID, mContentParent); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } } //過載方法2
@Override public void setContentView(View view) { setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); } //過載方法3 @Override public void setContentView(View view, ViewGroup.LayoutParams params) { if (mContentParent == null) { installDecor(); } else
{ mContentParent.removeAllViews(); } mContentParent.addView(view, params); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } } //... //... //... }

程式碼中可看出當mContentParent不為空時,清除mContentParent中的內容,若為空,則執行initallDecor方法。
以下為initallDecor方法:

private void installDecor() {
            if (mDecor == null) {
                mDecor = generateDecor();
                //...
                }
            }
            if (mContentParent == null) {
                mContentParent = generateLayout(mDecor);
                        //...
                    }
                }
            }
    }

通過generateLayout方法得到mDecor物件,再將mDecor作為形參執行generateLayout方法
以下為generateDecor方法程式碼:

protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
    }

以下為DecorView的結構圖
DecorView的結構圖

以下為generateLayout(mDecor)方法程式碼:

protected ViewGroup generateLayout(DecorView decor) {
//...

//省略一些設定Window樣式的程式碼,直接來看我們最關心的程式碼!
 ViewGroup contentParent =(ViewGroup)findViewById(ID_ANDROID_CONTENT);
                    //...           
                    return contentParent;
                }
         }

ID_ANDROID_CONTENT為R.id.content,就是下圖的那個FrameLayout
ID_ANDROID_CONTENT
所以ContentView即為這個FramLayout

重回PhoneWindiow類中對setContentView的實現程式碼:

public class PhoneWindow extends Window implements MenuBuilder.Callback {
    //...
    //...
    //...
    //過載方法1
    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else {
            mContentParent.removeAllViews();
        }
        mLayoutInflater.inflate(layoutResID, mContentParent);
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }
    //過載方法2
    @Override
    public void setContentView(View view) {
        setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }
    //過載方法3
    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        if (mContentParent == null) {
            installDecor();
        } else {
            mContentParent.removeAllViews();
        }
        mContentParent.addView(view, params);
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

    //...
    //...
    //...
}

總結

  首先判斷mContentParent是否為空,為空則獲取mContentParent,獲取mContentParent 需要呼叫installDecor方法獲取DecorView物件,然後通過DecorView物件再獲取ContentParentView物件。若不為空,則清除掉mContentParent裡的所有View檢視。
  接下來通過反射載入到我們傳入的佈局,接著下面會通過呼叫getCallBack得到一個CallBack物件cb,其實這個cb就是我們的Activity,接著會呼叫Activity的onContentChanged方法,這個方法是一個空實現,會在我們呼叫setContentView方法後呼叫。

注意:過載方法1是通過反射來傳入佈局的,而過載方法2和3是通過普通方法載入的,而這兩種方法的不同如下文所示

過載方法1使用載入佈局的方法是通過R.layout…反射得來的View,所以沒次呼叫setContentView方法拿到的View都是不同的。
而過載方法1和2都是通過直接傳View物件過來繫結佈局的,所以多次呼叫setContentView拿到的都是同一個View。