最簡單的Activity中的內容大致是這樣的:
public class MainActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_activity); } }
setContentView
一般來說我們設置頁面的內容視圖是都是通過setContentView方法,那么我們就以2.3源碼為例就來看看Activity中的setContentView到底做了什么吧。
1 /** 2 * Set the activity content from a layout resource. The resource will be 3 * inflated, adding all top-level views to the activity. 4 * 5 * @param layoutResID Resource ID to be inflated. 6 */ 7 public void setContentView(int layoutResID) { 8 getWindow().setContentView(layoutResID); 9 } 10 11 public Window getWindow() { 12 return mWindow; 13 } 14 15 16 private Window mWindow;
我們可以看到,實際上調用的mWindow的setContentView方法,在Android Touch事件分發過程這篇文章中我們已經指出Window的實現類為PhoneWindow類
1 @Override 2 public void setContentView(int layoutResID) { 3 if (mContentParent == null) { 4 installDecor(); // 1、生成DecorView 5 } else { 6 mContentParent.removeAllViews(); 7 } 8 mLayoutInflater.inflate(layoutResID, mContentParent);// 2、將layoutResId的布局添加到mContentParent中 9 final Callback cb = getCallback(); 10 if (cb != null) { 11 cb.onContentChanged(); 12 } 13 } 14 // 構建mDecor對象,並且初始化標題欄和Content Parent(我們要顯示的內容區域) 15 private void installDecor() { 16 if (mDecor == null) { 17 mDecor = generateDecor(); // 3、構建DecorView 18 mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); 19 mDecor.setIsRootNamespace(true); 20 } 21 if (mContentParent == null) { 22 mContentParent = generateLayout(mDecor); // 4、獲取ContentView容器,即顯示內容的區域 23 24 mTitleView = (TextView)findViewById(com.android.internal.R.id.title); 5、設置Title等 25 if (mTitleView != null) { 26 if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) { 27 View titleContainer = findViewById(com.android.internal.R.id.title_container); 28 if (titleContainer != null) { 29 titleContainer.setVisibility(View.GONE); 30 } else { 31 mTitleView.setVisibility(View.GONE); 32 } 33 if (mContentParent instanceof FrameLayout) { 34 ((FrameLayout)mContentParent).setForeground(null); 35 } 36 } else { 37 mTitleView.setText(mTitle); 38 } 39 } 40 } 41 } 42 43 protected DecorView generateDecor() { 44 return new DecorView(getContext(), -1); // 構建mDecor對象 45 }
我們可以看到,setContentView的基本流程簡單概括就是如下幾步:
1、構建mDecor對象。mDecor就是整個窗口的頂層視圖,它主要包含了Title和Content View兩個區域 (參考圖1中的兩個區域 ),Title區域就是我們的標題欄,Content View區域就是顯示我們xml布局內容中的區域。關於mDecor對象更多說明也請參考Android Touch事件分發過程這篇文章;
2、設置一些關於窗口的屬性,初始化標題欄區域和內容顯示區域;
這里比較復雜的就是generateLayout(mDecor)這個函數,我們一起來分析一下吧。
1 // 返回用於顯示我們設置的頁面內容的ViewGroup容器 2 protected ViewGroup generateLayout(DecorView decor) { 3 // Apply data from current theme. 4 // 1、獲取窗口的Style屬性 5 TypedArray a = getWindowStyle(); 6 7 if (false) { 8 System.out.println("From style:"); 9 String s = "Attrs:"; 10 for (int i = 0; i < com.android.internal.R.styleable.Window.length; i++) { 11 s = s + " " + Integer.toHexString(com.android.internal.R.styleable.Window[i]) + "=" 12 + a.getString(i); 13 } 14 System.out.println(s); 15 } 16 // 窗口是否是浮動的 17 mIsFloating = a.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating, false); 18 int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR) 19 & (~getForcedWindowFlags()); 20 if (mIsFloating) { 21 setLayout(WRAP_CONTENT, WRAP_CONTENT); 22 setFlags(0, flagsToUpdate); 23 } else { 24 setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate); 25 } 26 // 設置是否不顯示title區域 27 if (a.getBoolean(com.android.internal.R.styleable.Window_windowNoTitle, false)) { 28 requestFeature(FEATURE_NO_TITLE); 29 } 30 // 設置全屏的flag 31 if (a.getBoolean(com.android.internal.R.styleable.Window_windowFullscreen, false)) { 32 setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN&(~getForcedWindowFlags())); 33 } 34 35 if (a.getBoolean(com.android.internal.R.styleable.Window_windowShowWallpaper, false)) { 36 setFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags())); 37 } 38 39 WindowManager.LayoutParams params = getAttributes(); 40 // 設置輸入法模式 41 if (!hasSoftInputMode()) { 42 params.softInputMode = a.getInt( 43 com.android.internal.R.styleable.Window_windowSoftInputMode, 44 params.softInputMode); 45 } 46 47 if (a.getBoolean(com.android.internal.R.styleable.Window_backgroundDimEnabled, 48 mIsFloating)) { 49 /* All dialogs should have the window dimmed */ 50 if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) { 51 params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND; 52 } 53 params.dimAmount = a.getFloat( 54 android.R.styleable.Window_backgroundDimAmount, 0.5f); 55 } 56 // 窗口動畫 57 if (params.windowAnimations == 0) { 58 params.windowAnimations = a.getResourceId( 59 com.android.internal.R.styleable.Window_windowAnimationStyle, 0); 60 } 61 62 // The rest are only done if this window is not embedded; otherwise, 63 // the values are inherited from our container. 64 if (getContainer() == null) { 65 if (mBackgroundDrawable == null) { 66 if (mBackgroundResource == 0) { 67 mBackgroundResource = a.getResourceId( 68 com.android.internal.R.styleable.Window_windowBackground, 0); 69 } 70 if (mFrameResource == 0) { 71 mFrameResource = a.getResourceId(com.android.internal.R.styleable.Window_windowFrame, 0); 72 } 73 if (false) { 74 System.out.println("Background: " 75 + Integer.toHexString(mBackgroundResource) + " Frame: " 76 + Integer.toHexString(mFrameResource)); 77 } 78 } 79 mTextColor = a.getColor(com.android.internal.R.styleable.Window_textColor, 0xFF000000); 80 } 81 82 // Inflate the window decor. 83 // 2、根據一些屬性來選擇不同的頂層視圖布局,例如設置了FEATURE_NO_TITLE的屬性,那么就選擇沒有Title區域的那么布局; 84 // layoutResource布局就是整個Activity的布局,其中含有title區域和content區域,content區域就是用來顯示我通過 85 // setContentView設置進來的內容區域,也就是我們要顯示的視圖。 86 87 int layoutResource; 88 int features = getLocalFeatures(); 89 // System.out.println("Features: 0x" + Integer.toHexString(features)); 90 if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) { 91 if (mIsFloating) { 92 layoutResource = com.android.internal.R.layout.dialog_title_icons; 93 } else { 94 layoutResource = com.android.internal.R.layout.screen_title_icons; 95 } 96 // System.out.println("Title Icons!"); 97 } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0) { 98 // Special case for a window with only a progress bar (and title). 99 // XXX Need to have a no-title version of embedded windows. 100 layoutResource = com.android.internal.R.layout.screen_progress; 101 // System.out.println("Progress!"); 102 } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) { 103 // Special case for a window with a custom title. 104 // If the window is floating, we need a dialog layout 105 if (mIsFloating) { 106 layoutResource = com.android.internal.R.layout.dialog_custom_title; 107 } else { 108 layoutResource = com.android.internal.R.layout.screen_custom_title; 109 } 110 } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) { 111 // If no other features and not embedded, only need a title. 112 // If the window is floating, we need a dialog layout 113 if (mIsFloating) { 114 layoutResource = com.android.internal.R.layout.dialog_title; 115 } else { 116 layoutResource = com.android.internal.R.layout.screen_title; 117 } 118 // System.out.println("Title!"); 119 } else { 120 // Embedded, so no decoration is needed. 121 layoutResource = com.android.internal.R.layout.screen_simple; 122 // System.out.println("Simple!"); 123 } 124 125 mDecor.startChanging(); 126 // 3、加載視圖 127 View in = mLayoutInflater.inflate(layoutResource, null); 128 // 4、將layoutResource的內容添加到mDecor中 129 decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); 130 // 5、獲取到我們的內容顯示區域,這是一個ViewGroup類型的,其實是FrameLayout 131 ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); 132 if (contentParent == null) { 133 throw new RuntimeException("Window couldn't find content container view"); 134 } 135 136 if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) { 137 ProgressBar progress = getCircularProgressBar(false); 138 if (progress != null) { 139 progress.setIndeterminate(true); 140 } 141 } 142 143 // 6、設置一些背景、title等屬性 144 // Remaining setup -- of background and title -- that only applies 145 // to top-level windows. 146 if (getContainer() == null) { 147 Drawable drawable = mBackgroundDrawable; 148 if (mBackgroundResource != 0) { 149 drawable = getContext().getResources().getDrawable(mBackgroundResource); 150 } 151 mDecor.setWindowBackground(drawable); 152 drawable = null; 153 if (mFrameResource != 0) { 154 drawable = getContext().getResources().getDrawable(mFrameResource); 155 } 156 mDecor.setWindowFrame(drawable); 157 158 // System.out.println("Text=" + Integer.toHexString(mTextColor) + 159 // " Sel=" + Integer.toHexString(mTextSelectedColor) + 160 // " Title=" + Integer.toHexString(mTitleColor)); 161 162 if (mTitleColor == 0) { 163 mTitleColor = mTextColor; 164 } 165 166 if (mTitle != null) { 167 setTitle(mTitle); 168 } 169 setTitleColor(mTitleColor); 170 } 171 172 mDecor.finishChanging(); 173 174 return contentParent;
其實也就是這么幾個步驟:
1、獲取用戶設置的一些屬性與Flag;
2、根據一些屬性選擇不同的頂層視圖布局,例如FEATURE_NO_TITLE則選擇沒有title的布局文件等;這里我們看一個與圖1中符合的頂層布局吧,即layoutResource = c
1 <?xml version="1.0" encoding="utf-8"?> 2 3 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 4 android:orientation="vertical" 5 android:fitsSystemWindows="true"> 6 <!-- Popout bar for action modes --> 7 <ViewStub android:id="@+id/action_mode_bar_stub" 8 android:inflatedId="@+id/action_mode_bar" 9 android:layout="@layout/action_mode_bar" 10 android:layout_width="match_parent" 11 android:layout_height="wrap_content" /> 12 <!-- title區域--> 13 <FrameLayout 14 android:layout_width="match_parent" 15 android:layout_height="?android:attr/windowTitleSize" 16 style="?android:attr/windowTitleBackgroundStyle"> 17 <TextView android:id="@android:id/title" 18 style="?android:attr/windowTitleStyle" 19 android:background="@null" 20 android:fadingEdge="horizontal" 21 android:gravity="center_vertical" 22 android:layout_width="match_parent" 23 android:layout_height="match_parent" /> 24 </FrameLayout> 25 <!--內容顯示區域, 例如main_activity.xml布局就會被放到這個ViewGroup下面 --> 26 <FrameLayout android:id="@android:id/content" 27 android:layout_width="match_parent" 28 android:layout_height="0dip" 29 android:layout_weight="1" 30 android:foregroundGravity="fill_horizontal|top" 31 android:foreground="?android:attr/windowContentOverlay" /> 32 </LinearLayout>
我們可以看到有兩個區域,即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函數:
1 public void setContentView(int layoutResID) { 2 if (mContentParent == null) { 3 installDecor(); // 1、生成DecorView,並且根據窗口屬性加載頂級視圖布局、獲取mContentParent、設置一些基本屬性等 4 } else { 5 mContentParent.removeAllViews(); 6 } 7 mLayoutInflater.inflate(layoutResID, mContentParent);// 2、將layoutResId加載到mContentParent中,這里的layoutResId就是我們的main_activity.xml 8 final Callback cb = getCallback(); 9 if (cb != null) { 10 cb.onContentChanged(); 11 } 12 }
我們看看LayoutInflater的inflate函數吧 :
1 /** 2 * Inflate a new view hierarchy from the specified xml resource. Throws 3 * {@link InflateException} if there is an error. 4 * 5 * @param resource ID for an XML layout resource to load (e.g., 6 * <code>R.layout.main_page</code>) 7 * @param root Optional view to be the parent of the generated hierarchy. 8 * @return The root View of the inflated hierarchy. If root was supplied, 9 * this is the root View; otherwise it is the root of the inflated 10 * XML file. 11 */ 12 public View inflate(int resource, ViewGroup root) { 13 return inflate(resource, root, root != null); 14 } 15 16 /** 17 * Inflate a new view hierarchy from the specified xml resource. Throws 18 * {@link InflateException} if there is an error. 19 * 20 * @param resource ID for an XML layout resource to load (e.g., 21 * <code>R.layout.main_page</code>) 22 * @param root Optional view to be the parent of the generated hierarchy (if 23 * <em>attachToRoot</em> is true), or else simply an object that 24 * provides a set of LayoutParams values for root of the returned 25 * hierarchy (if <em>attachToRoot</em> is false.) 26 * @param attachToRoot Whether the inflated hierarchy should be attached to 27 * the root parameter? If false, root is only used to create the 28 * correct subclass of LayoutParams for the root view in the XML. 29 * @return The root View of the inflated hierarchy. If root was supplied and 30 * attachToRoot is true, this is root; otherwise it is the root of 31 * the inflated XML file. 32 */ 33 public View inflate(int resource, ViewGroup root, boolean attachToRoot) { 34 if (DEBUG) System.out.println("INFLATING from resource: " + resource); 35 XmlResourceParser parser = getContext().getResources().getLayout(resource); 36 try { 37 return inflate(parser, root, attachToRoot); 38 } finally { 39 parser.close(); 40 } 41 }
實際上就是將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的子類。我們看官方定義 :
TextView、Button、ImageView、FrameLayout、LinearLayout、ListView等都是View的子類。
這樣,ViewGroup類型的視圖管理嵌套在里面的ViewGroup以及View控件組成了豐富多彩的用戶界面。例如我們開篇的Hello World的視圖結構是這樣的 :
總結
整個窗口由Title區域和Content區域組成,Content區域就是我們要顯示內容的區域,在這個區域中mContentParent是根ViewGroup,由mContentParent組織、管理其子視圖,從而構建整個視圖樹。當Activity啟動時,就將這些內容就會顯示在手機上。