Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android View系統分析之從setContentView說開來(一)

Android View系統分析之從setContentView說開來(一)

編輯:關於Android編程

今天是中秋節,先祝各位中秋快樂吧。作為北漂的人,對於過節最大的感觸就是沒氣氛~ 中秋是一個特別重要的節日,小的時候過中秋都是特別快樂的,有月餅吃,和家人上月,過完中秋要去親戚家拜訪等等。現在對於我們來說也就是一個節日罷了,窩在家裡看點電視、看點書、吃頓好的,雖說生活好了,但日子過得沒啥滋味。廢話不多說,開始今天的學習吧。

Hello World

對於學習編程的人而言,大多數人第一個項目都是著名的"Hello World",自從K&R開了這個先例,後面的人就很少有打破的。學習Android開發也是這樣,我們第一次創建應用,估計也就是運行程序,然後在模擬器上輸出一個Hello World,我們看到最簡單的Activity中的內容大致是這樣的:

public class MainActivity extends Activity {


	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main_activity);
	}

}
main_activity.xml大致是這樣的 :



    

然後執行程序,我們就可以看到模擬器中的Hello World了。


圖1

我們在整個過程中做的事情很少,在我們的main_activity.xml我們只有一個顯示文本的TextView,但是在上圖中卻還多了一個title。我們好奇的是整個過程是怎麼工作的?對於大型系統來說細節總是復雜的,在下水平有限,所以我們今天只來理一下它的基本脈絡。

setContentView

一般來說我們設置頁面的內容視圖是都是通過setContentView方法,那麼我們就以2.3源碼為例就來看看Activity中的setContentView到底做了什麼吧。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD48cD48L3A+PHByZSBjbGFzcz0="brush:java;"> /** * Set the activity content from a layout resource. The resource will be * inflated, adding all top-level views to the activity. * * @param layoutResID Resource ID to be inflated. */ public void setContentView(int layoutResID) { getWindow().setContentView(layoutResID); } public Window getWindow() { return mWindow; } private Window mWindow;

我們可以看到,實際上調用的mWindow的setContentView方法,在Android Touch事件分發過程這篇文章中我們已經指出Window的實現類為PhoneWindow類,我們就移步到PhoneWindow的setConentView吧,核心源碼如下 :

    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();			// 1、生成DecorView
        } else {
            mContentParent.removeAllViews();
        }
        mLayoutInflater.inflate(layoutResID, mContentParent);// 2、將layoutResId的布局添加到mContentParent中
        final Callback cb = getCallback();
        if (cb != null) {
            cb.onContentChanged();
        }
    }
        // 構建mDecor對象,並且初始化標題欄和Content Parent(我們要顯示的內容區域)
        private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();          // 3、構建DecorView
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);              // 4、獲取ContentView容器,即顯示內容的區域

            mTitleView = (TextView)findViewById(com.android.internal.R.id.title); 5、設置Title等
            if (mTitleView != null) {
                if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
                    View titleContainer = findViewById(com.android.internal.R.id.title_container);
                    if (titleContainer != null) {
                        titleContainer.setVisibility(View.GONE);
                    } else {
                        mTitleView.setVisibility(View.GONE);
                    }
                    if (mContentParent instanceof FrameLayout) {
                        ((FrameLayout)mContentParent).setForeground(null);
                    }
                } else {
                    mTitleView.setText(mTitle);
                }
            }
        }
    }

        protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);    // 構建mDecor對象
    }
我們可以看到,setContentView的基本流程簡單概括就是如下幾步:

1、構建mDecor對象。mDecor就是整個窗口的頂層視圖,它主要包含了Title和Content View兩個區域 (參考圖1中的兩個區域 ),Title區域就是我們的標題欄,Content View區域就是顯示我們xml布局內容中的區域。關於mDecor對象更多說明也請參考Android Touch事件分發過程這篇文章;

2、設置一些關於窗口的屬性,初始化標題欄區域和內容顯示區域;

這裡比較復雜的就是generateLayout(mDecor)這個函數,我們一起來分析一下吧。

     // 返回用於顯示我們設置的頁面內容的ViewGroup容器
     protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.
        // 1、獲取窗口的Style屬性
        TypedArray a = getWindowStyle();

        if (false) {
            System.out.println("From style:");
            String s = "Attrs:";
            for (int i = 0; i < com.android.internal.R.styleable.Window.length; i++) {
                s = s + " " + Integer.toHexString(com.android.internal.R.styleable.Window[i]) + "="
                        + a.getString(i);
            }
            System.out.println(s);
        }
        // 窗口是否是浮動的
        mIsFloating = a.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating, false);
        int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
                & (~getForcedWindowFlags());
        if (mIsFloating) {
            setLayout(WRAP_CONTENT, WRAP_CONTENT);
            setFlags(0, flagsToUpdate);
        } else {
            setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
        }
        // 設置是否不顯示title區域
        if (a.getBoolean(com.android.internal.R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        }
        // 設置全屏的flag
        if (a.getBoolean(com.android.internal.R.styleable.Window_windowFullscreen, false)) {
            setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN&(~getForcedWindowFlags()));
        }

        if (a.getBoolean(com.android.internal.R.styleable.Window_windowShowWallpaper, false)) {
            setFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags()));
        }

        WindowManager.LayoutParams params = getAttributes();
        // 設置輸入法模式
        if (!hasSoftInputMode()) {
            params.softInputMode = a.getInt(
                    com.android.internal.R.styleable.Window_windowSoftInputMode,
                    params.softInputMode);
        }

        if (a.getBoolean(com.android.internal.R.styleable.Window_backgroundDimEnabled,
                mIsFloating)) {
            /* All dialogs should have the window dimmed */
            if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) {
                params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
            }
            params.dimAmount = a.getFloat(
                    android.R.styleable.Window_backgroundDimAmount, 0.5f);
        }
        // 窗口動畫
        if (params.windowAnimations == 0) {
            params.windowAnimations = a.getResourceId(
                    com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
        }

        // The rest are only done if this window is not embedded; otherwise,
        // the values are inherited from our container.
        if (getContainer() == null) {
            if (mBackgroundDrawable == null) {
                if (mBackgroundResource == 0) {
                    mBackgroundResource = a.getResourceId(
                            com.android.internal.R.styleable.Window_windowBackground, 0);
                }
                if (mFrameResource == 0) {
                    mFrameResource = a.getResourceId(com.android.internal.R.styleable.Window_windowFrame, 0);
                }
                if (false) {
                    System.out.println("Background: "
                            + Integer.toHexString(mBackgroundResource) + " Frame: "
                            + Integer.toHexString(mFrameResource));
                }
            }
            mTextColor = a.getColor(com.android.internal.R.styleable.Window_textColor, 0xFF000000);
        }

        // Inflate the window decor. 
        // 2、根據一些屬性來選擇不同的頂層視圖布局,例如設置了FEATURE_NO_TITLE的屬性,那麼就選擇沒有Title區域的那麼布局;
        // layoutResource布局就是整個Activity的布局,其中含有title區域和content區域,content區域就是用來顯示我通過
        // setContentView設置進來的內容區域,也就是我們要顯示的視圖。

        int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
            if (mIsFloating) {
                layoutResource = com.android.internal.R.layout.dialog_title_icons;
            } else {
                layoutResource = com.android.internal.R.layout.screen_title_icons;
            }
            // System.out.println("Title Icons!");
        } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0) {
            // Special case for a window with only a progress bar (and title).
            // XXX Need to have a no-title version of embedded windows.
            layoutResource = com.android.internal.R.layout.screen_progress;
            // System.out.println("Progress!");
        } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
            // Special case for a window with a custom title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                layoutResource = com.android.internal.R.layout.dialog_custom_title;
            } else {
                layoutResource = com.android.internal.R.layout.screen_custom_title;
            }
        } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
            // If no other features and not embedded, only need a title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                layoutResource = com.android.internal.R.layout.dialog_title;
            } else {
                layoutResource = com.android.internal.R.layout.screen_title;
            }
            // System.out.println("Title!");
        } else {
            // Embedded, so no decoration is needed.
            layoutResource = com.android.internal.R.layout.screen_simple;
            // System.out.println("Simple!");
        }

        mDecor.startChanging();
        // 3、加載視圖
        View in = mLayoutInflater.inflate(layoutResource, null);
        // 4、將layoutResource的內容添加到mDecor中
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        // 5、獲取到我們的內容顯示區域,這是一個ViewGroup類型的,其實是FrameLayout
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
            ProgressBar progress = getCircularProgressBar(false);
            if (progress != null) {
                progress.setIndeterminate(true);
            }
        }

        // 6、設置一些背景、title等屬性
        // Remaining setup -- of background and title -- that only applies
        // to top-level windows.
        if (getContainer() == null) {
            Drawable drawable = mBackgroundDrawable;
            if (mBackgroundResource != 0) {
                drawable = getContext().getResources().getDrawable(mBackgroundResource);
            }
            mDecor.setWindowBackground(drawable);
            drawable = null;
            if (mFrameResource != 0) {
                drawable = getContext().getResources().getDrawable(mFrameResource);
            }
            mDecor.setWindowFrame(drawable);

            // System.out.println("Text=" + Integer.toHexString(mTextColor) +
            // " Sel=" + Integer.toHexString(mTextSelectedColor) +
            // " Title=" + Integer.toHexString(mTitleColor));

            if (mTitleColor == 0) {
                mTitleColor = mTextColor;
            }

            if (mTitle != null) {
                setTitle(mTitle);
            }
            setTitleColor(mTitleColor);
        }

        mDecor.finishChanging();

        return contentParent;
    }
其實也就是這麼幾個步驟:

1、獲取用戶設置的一些屬性與Flag;

2、根據一些屬性選擇不同的頂層視圖布局,例如FEATURE_NO_TITLE則選擇沒有title的布局文件等;這裡我們看一個與圖1中符合的頂層布局吧,即layoutResource = com.android.internal.R.layout.screen_title的情形:




    
    
    
    <frameLayout
        android:layout_width="match_parent" 
        android:layout_height="?android:attr/windowTitleSize"
        >
        
    </frameLayout>
    
    <frameLayout android:id="@android:id/content"
        android:layout_width="match_parent" 
        android:layout_height="0dip"
        android:layout_weight="1"
        android:foregroundGravity="fill_horizontal|top"
        android:foreground="?android:attr/windowContentOverlay" />

我們可以看到有兩個區域,即title區域和content區域,generateLayout函數中的

 // 5、獲取到我們的內容顯示區域,這是一個ViewGroup類型的,其實是FrameLayout
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

獲取的就是xml中id為content的FrameLayout,這個content就是我們的內容顯示區域。整個布局對應的效果如下 :


這兩個區域就組成了mDecor視圖,我們的main_activity.xml就是放在內容視圖這個區域的。

3、加載頂層布局文件,轉換為View,將其添加到mDecor中;

4、獲取內容容器Content Parent,即用於顯示我們的內容的區域;

5、設置一些背景圖和title等。

在經過這幾步,我們就得到了mContentParent,這就是用來裝載我們的視圖的ViewGroup。再回過頭來看setContentView函數:

    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();			// 1、生成DecorView,並且根據窗口屬性加載頂級視圖布局、獲取mContentParent、設置一些基本屬性等
        } else {
            mContentParent.removeAllViews();
        }
        mLayoutInflater.inflate(layoutResID, mContentParent);// 2、將layoutResId加載到mContentParent中,這裡的layoutResId就是我們的main_activity.xml
        final Callback cb = getCallback();
        if (cb != null) {
            cb.onContentChanged();
        }
    }
我們看看LayoutInflater的inflate函數吧 :

    /**
     * Inflate a new view hierarchy from the specified xml resource. Throws
     * {@link InflateException} if there is an error.
     * 
     * @param resource ID for an XML layout resource to load (e.g.,
     *        R.layout.main_page)
     * @param root Optional view to be the parent of the generated hierarchy.
     * @return The root View of the inflated hierarchy. If root was supplied,
     *         this is the root View; otherwise it is the root of the inflated
     *         XML file.
     */
    public View inflate(int resource, ViewGroup root) {
        return inflate(resource, root, root != null);
    }

    /**
     * Inflate a new view hierarchy from the specified xml resource. Throws
     * {@link InflateException} if there is an error.
     * 
     * @param resource ID for an XML layout resource to load (e.g.,
     *        R.layout.main_page)
     * @param root Optional view to be the parent of the generated hierarchy (if
     *        attachToRoot is true), or else simply an object that
     *        provides a set of LayoutParams values for root of the returned
     *        hierarchy (if attachToRoot is false.)
     * @param attachToRoot Whether the inflated hierarchy should be attached to
     *        the root parameter? If false, root is only used to create the
     *        correct subclass of LayoutParams for the root view in the XML.
     * @return The root View of the inflated hierarchy. If root was supplied and
     *         attachToRoot is true, this is root; otherwise it is the root of
     *         the inflated XML file.
     */
    public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
        if (DEBUG) System.out.println("INFLATING from resource: " + resource);
        XmlResourceParser parser = getContext().getResources().getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }
實際上就是將layoutResId這個布局的視圖附加到mContentParent中。

DecorView

移步 : DecorView 。

ViewGroup

ViewGroup從語義上來說就是視圖組,它也繼承自View類,它其實就是視圖的容器。我們看官方的定義 :

 * A ViewGroup is a special view that can contain other views
 * (called children.) The view group is the base class for layouts and views
 * containers. This class also defines the
 * {@link android.view.ViewGroup.LayoutParams} class which serves as the base
 * class for layouts parameters.
我們通過ViewGroup來組織、管理子視圖,例如我們常見的FrameLayout、LinearLayout、RelativeLayout、ListView等都是ViewGroup類型,總之只要能包含其他View或者ViewGroup的都是ViewGroup類型。使用ViewGroup來構建視圖樹。



View

View就是UI界面上的一個可見的組件,任何在UI上可見的都為View的子類。我們看官方定義 :

 * This class represents the basic building block for user interface components. A View
 * occupies a rectangular area on the screen and is responsible for drawing and
 * event handling. View is the base class for widgets, which are
 * used to create interactive UI components (buttons, text fields, etc.). The
 * {@link android.view.ViewGroup} subclass is the base class for layouts, which
 * are invisible containers that hold other Views (or other ViewGroups) and define
 * their layout properties.

TextView、Button、ImageView、FrameLayout、LinearLayout、ListView等都是View的子類。

總結


整個窗口由Title區域和Content區域組成,Content區域就是我們要顯示內容的區域,在這個區域中mContentParent是根ViewGroup,由mContentParent組織、管理其子視圖,從而構建整個視圖樹。當Activity啟動時,就將這些內容就會顯示在手機上。



  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved