Android高级UI开发(三十三)android setContentView布局加载流程(一) 布局结构

想必大家都写过Activity中onCreate函数里的setContentView(R.layout.main);这行代码吧。 这行代码是如何将我们的布局显示到Activity里的?今天就带大家来解析一番。

首先,我们进入到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. * @see #setContentView(android.view.View)
  8. * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
  9. */
  10. public void setContentView(@LayoutRes int layoutResID) {
  11. getWindow().setContentView(layoutResID);
  12. initWindowDecorActionBar();
  13. }

我们发现它的内部实质是调用了getWindow().setContentView(layoutResID)。那我们就来看一下这个getWindow(),进入后代码如下:


  
  1. public Window getWindow() {
  2. return mWindow;
  3. }

我看了下Window类,发现它是一个抽象类,后来找到了它的实现类是PhoneWindow,那我们就看PhoneWindow的setContentView函数,代码如下:


  
  1. @Override
  2. public void setContentView(int layoutResID) {
  3. // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
  4. // decor, when theme attributes and the like are crystalized. Do not check the feature
  5. // before this happens.
  6. if (mContentParent == null) {
  7. installDecor();
  8. } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
  9. mContentParent.removeAllViews();
  10. }
  11. if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
  12. final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
  13. getContext());
  14. transitionTo(newScene);
  15. } else {
  16. mLayoutInflater.inflate(layoutResID, mContentParent);
  17. }
  18. mContentParent.requestApplyInsets();
  19. final Callback cb = getCallback();
  20. if (cb != null && !isDestroyed()) {
  21. cb.onContentChanged();
  22. }
  23. mContentParentExplicitlySet = true;
  24. }

我们先看一下installDecor()函数,它目的是创建一个DectorView ,该view继承于FrameLayout,installDecor()的代码如下:


  
  1. private void installDecor() {
  2. mForceDecorInstall = false;
  3. if (mDecor == null) {
  4. mDecor = generateDecor(-1);
  5. mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
  6. mDecor.setIsRootNamespace(true);
  7. if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
  8. mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
  9. }
  10. } else {
  11. mDecor.setWindow(this);
  12. }
  13. if (mContentParent == null) {
  14. mContentParent = generateLayout(mDecor);
  15. // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
  16. mDecor.makeOptionalFitsSystemWindows();
  17. 。。。。。。
  18. }

其中第4行:generateDecor函数,就创建了一个继承于FrameLayout的DectorView视图,其中mDector的类型是DectorView;

       还有 mContentParent = generateLayout(mDecor)这行代码,它的作用是往DectorView里添加了一个ID为R.id.content视图。然后返回了这个content视图,并赋值给mContentParent。  这个R.id.content视图是干什么的呢?在此提前剧透一下,它就是用来包裹我们setContentView(R.layout.main).中 main.xml布局的,换句话说,它是我们页面布局的根布局。

        那好,那当前我们所认识的视图结构图就是:PhoneWindow是Activity的一个窗口,这个PhoneWindow里有一个DectorView,它是这个Activity的根布局,因为它里面的mContentParent(R.id.content)包裹了main.xml里的所有布局视图。

视图结构示意图如下:

上图是我们画的一个Activity视图结构图,我们深入到mContentParent = generateLayout(mDecor),分析这个generateLayout函数里,看看能不能再完善一下我们的视图结构图。generateLayout(mDecor)的代码如下:

 

 


  
  1. protected ViewGroup generateLayout(DecorView decor) {
  2. // Apply data from current theme.
  3. TypedArray a = getWindowStyle();
  4. if (false) {
  5. System.out.println("From style:");
  6. String s = "Attrs:";
  7. for (int i = 0; i < R.styleable.Window.length; i++) {
  8. s = s + " " + Integer.toHexString(R.styleable.Window[i]) + "="
  9. + a.getString(i);
  10. }
  11. System.out.println(s);
  12. }
  13. mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
  14. int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
  15. & (~getForcedWindowFlags());
  16. if (mIsFloating) {
  17. setLayout(WRAP_CONTENT, WRAP_CONTENT);
  18. setFlags(0, flagsToUpdate);
  19. } else {
  20. setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
  21. }
  22. if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
  23. requestFeature(FEATURE_NO_TITLE);
  24. } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
  25. // Don't allow an action bar if there is no title.
  26. requestFeature(FEATURE_ACTION_BAR);
  27. }
  28. if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
  29. requestFeature(FEATURE_ACTION_BAR_OVERLAY);
  30. }
  31. if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) {
  32. requestFeature(FEATURE_ACTION_MODE_OVERLAY);
  33. }
  34. if (a.getBoolean(R.styleable.Window_windowSwipeToDismiss, false)) {
  35. requestFeature(FEATURE_SWIPE_TO_DISMISS);
  36. }
  37. if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
  38. setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
  39. }
  40. if (a.getBoolean(R.styleable.Window_windowTranslucentStatus,
  41. false)) {
  42. setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS
  43. & (~getForcedWindowFlags()));
  44. }
  45. if (a.getBoolean(R.styleable.Window_windowTranslucentNavigation,
  46. false)) {
  47. setFlags(FLAG_TRANSLUCENT_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION
  48. & (~getForcedWindowFlags()));
  49. }
  50. if (a.getBoolean(R.styleable.Window_windowOverscan, false)) {
  51. setFlags(FLAG_LAYOUT_IN_OVERSCAN, FLAG_LAYOUT_IN_OVERSCAN&(~getForcedWindowFlags()));
  52. }
  53. if (a.getBoolean(R.styleable.Window_windowShowWallpaper, false)) {
  54. setFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags()));
  55. }
  56. if (a.getBoolean(R.styleable.Window_windowEnableSplitTouch,
  57. getContext().getApplicationInfo().targetSdkVersion
  58. >= android.os.Build.VERSION_CODES.HONEYCOMB)) {
  59. setFlags(FLAG_SPLIT_TOUCH, FLAG_SPLIT_TOUCH&(~getForcedWindowFlags()));
  60. }
  61. a.getValue(R.styleable.Window_windowMinWidthMajor, mMinWidthMajor);
  62. a.getValue(R.styleable.Window_windowMinWidthMinor, mMinWidthMinor);
  63. if (DEBUG) Log.d(TAG, "Min width minor: " + mMinWidthMinor.coerceToString()
  64. + ", major: " + mMinWidthMajor.coerceToString());
  65. if (a.hasValue(R.styleable.Window_windowFixedWidthMajor)) {
  66. if (mFixedWidthMajor == null) mFixedWidthMajor = new TypedValue();
  67. a.getValue(R.styleable.Window_windowFixedWidthMajor,
  68. mFixedWidthMajor);
  69. }
  70. if (a.hasValue(R.styleable.Window_windowFixedWidthMinor)) {
  71. if (mFixedWidthMinor == null) mFixedWidthMinor = new TypedValue();
  72. a.getValue(R.styleable.Window_windowFixedWidthMinor,
  73. mFixedWidthMinor);
  74. }
  75. if (a.hasValue(R.styleable.Window_windowFixedHeightMajor)) {
  76. if (mFixedHeightMajor == null) mFixedHeightMajor = new TypedValue();
  77. a.getValue(R.styleable.Window_windowFixedHeightMajor,
  78. mFixedHeightMajor);
  79. }
  80. if (a.hasValue(R.styleable.Window_windowFixedHeightMinor)) {
  81. if (mFixedHeightMinor == null) mFixedHeightMinor = new TypedValue();
  82. a.getValue(R.styleable.Window_windowFixedHeightMinor,
  83. mFixedHeightMinor);
  84. }
  85. if (a.getBoolean(R.styleable.Window_windowContentTransitions, false)) {
  86. requestFeature(FEATURE_CONTENT_TRANSITIONS);
  87. }
  88. if (a.getBoolean(R.styleable.Window_windowActivityTransitions, false)) {
  89. requestFeature(FEATURE_ACTIVITY_TRANSITIONS);
  90. }
  91. mIsTranslucent = a.getBoolean(R.styleable.Window_windowIsTranslucent, false);
  92. final Context context = getContext();
  93. final int targetSdk = context.getApplicationInfo().targetSdkVersion;
  94. final boolean targetPreHoneycomb = targetSdk < android.os.Build.VERSION_CODES.HONEYCOMB;
  95. final boolean targetPreIcs = targetSdk < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH;
  96. final boolean targetPreL = targetSdk < android.os.Build.VERSION_CODES.LOLLIPOP;
  97. final boolean targetHcNeedsOptions = context.getResources().getBoolean(
  98. R.bool.target_honeycomb_needs_options_menu);
  99. final boolean noActionBar = !hasFeature(FEATURE_ACTION_BAR) || hasFeature(FEATURE_NO_TITLE);
  100. if (targetPreHoneycomb || (targetPreIcs && targetHcNeedsOptions && noActionBar)) {
  101. setNeedsMenuKey(WindowManager.LayoutParams.NEEDS_MENU_SET_TRUE);
  102. } else {
  103. setNeedsMenuKey(WindowManager.LayoutParams.NEEDS_MENU_SET_FALSE);
  104. }
  105. if (!mForcedStatusBarColor) {
  106. mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);
  107. }
  108. if (!mForcedNavigationBarColor) {
  109. mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);
  110. mNavigationBarDividerColor = a.getColor(R.styleable.Window_navigationBarDividerColor,
  111. 0x00000000);
  112. }
  113. WindowManager.LayoutParams params = getAttributes();
  114. // Non-floating windows on high end devices must put up decor beneath the system bars and
  115. // therefore must know about visibility changes of those.
  116. if (!mIsFloating) {
  117. if (!targetPreL && a.getBoolean(
  118. R.styleable.Window_windowDrawsSystemBarBackgrounds,
  119. false)) {
  120. setFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
  121. FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS & ~getForcedWindowFlags());
  122. }
  123. if (mDecor.mForceWindowDrawsStatusBarBackground) {
  124. params.privateFlags |= PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND;
  125. }
  126. }
  127. if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) {
  128. decor.setSystemUiVisibility(
  129. decor.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
  130. }
  131. if (a.getBoolean(R.styleable.Window_windowLightNavigationBar, false)) {
  132. decor.setSystemUiVisibility(
  133. decor.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
  134. }
  135. if (a.hasValue(R.styleable.Window_windowLayoutInDisplayCutoutMode)) {
  136. int mode = a.getInt(R.styleable.Window_windowLayoutInDisplayCutoutMode, -1);
  137. if (mode < LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
  138. || mode > LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER) {
  139. throw new UnsupportedOperationException("Unknown windowLayoutInDisplayCutoutMode: "
  140. + a.getString(R.styleable.Window_windowLayoutInDisplayCutoutMode));
  141. }
  142. params.layoutInDisplayCutoutMode = mode;
  143. }
  144. if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion
  145. >= android.os.Build.VERSION_CODES.HONEYCOMB) {
  146. if (a.getBoolean(
  147. R.styleable.Window_windowCloseOnTouchOutside,
  148. false)) {
  149. setCloseOnTouchOutsideIfNotSet(true);
  150. }
  151. }
  152. if (!hasSoftInputMode()) {
  153. params.softInputMode = a.getInt(
  154. R.styleable.Window_windowSoftInputMode,
  155. params.softInputMode);
  156. }
  157. if (a.getBoolean(R.styleable.Window_backgroundDimEnabled,
  158. mIsFloating)) {
  159. /* All dialogs should have the window dimmed */
  160. if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) {
  161. params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
  162. }
  163. if (!haveDimAmount()) {
  164. params.dimAmount = a.getFloat(
  165. android.R.styleable.Window_backgroundDimAmount, 0.5f);
  166. }
  167. }
  168. if (params.windowAnimations == 0) {
  169. params.windowAnimations = a.getResourceId(
  170. R.styleable.Window_windowAnimationStyle, 0);
  171. }
  172. // The rest are only done if this window is not embedded; otherwise,
  173. // the values are inherited from our container.
  174. if (getContainer() == null) {
  175. if (mBackgroundDrawable == null) {
  176. if (mBackgroundResource == 0) {
  177. mBackgroundResource = a.getResourceId(
  178. R.styleable.Window_windowBackground, 0);
  179. }
  180. if (mFrameResource == 0) {
  181. mFrameResource = a.getResourceId(R.styleable.Window_windowFrame, 0);
  182. }
  183. mBackgroundFallbackResource = a.getResourceId(
  184. R.styleable.Window_windowBackgroundFallback, 0);
  185. if (false) {
  186. System.out.println("Background: "
  187. + Integer.toHexString(mBackgroundResource) + " Frame: "
  188. + Integer.toHexString(mFrameResource));
  189. }
  190. }
  191. if (mLoadElevation) {
  192. mElevation = a.getDimension(R.styleable.Window_windowElevation, 0);
  193. }
  194. mClipToOutline = a.getBoolean(R.styleable.Window_windowClipToOutline, false);
  195. mTextColor = a.getColor(R.styleable.Window_textColor, Color.TRANSPARENT);
  196. }
  197. // Inflate the window decor.
  198. int layoutResource;
  199. int features = getLocalFeatures();
  200. // System.out.println("Features: 0x" + Integer.toHexString(features));
  201. if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
  202. layoutResource = R.layout.screen_swipe_dismiss;
  203. setCloseOnSwipeEnabled(true);
  204. } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
  205. if (mIsFloating) {
  206. TypedValue res = new TypedValue();
  207. getContext().getTheme().resolveAttribute(
  208. R.attr.dialogTitleIconsDecorLayout, res, true);
  209. layoutResource = res.resourceId;
  210. } else {
  211. layoutResource = R.layout.screen_title_icons;
  212. }
  213. // XXX Remove this once action bar supports these features.
  214. removeFeature(FEATURE_ACTION_BAR);
  215. // System.out.println("Title Icons!");
  216. } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
  217. && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
  218. // Special case for a window with only a progress bar (and title).
  219. // XXX Need to have a no-title version of embedded windows.
  220. layoutResource = R.layout.screen_progress;
  221. // System.out.println("Progress!");
  222. } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
  223. // Special case for a window with a custom title.
  224. // If the window is floating, we need a dialog layout
  225. if (mIsFloating) {
  226. TypedValue res = new TypedValue();
  227. getContext().getTheme().resolveAttribute(
  228. R.attr.dialogCustomTitleDecorLayout, res, true);
  229. layoutResource = res.resourceId;
  230. } else {
  231. layoutResource = R.layout.screen_custom_title;
  232. }
  233. // XXX Remove this once action bar supports these features.
  234. removeFeature(FEATURE_ACTION_BAR);
  235. } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
  236. // If no other features and not embedded, only need a title.
  237. // If the window is floating, we need a dialog layout
  238. if (mIsFloating) {
  239. TypedValue res = new TypedValue();
  240. getContext().getTheme().resolveAttribute(
  241. R.attr.dialogTitleDecorLayout, res, true);
  242. layoutResource = res.resourceId;
  243. } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
  244. layoutResource = a.getResourceId(
  245. R.styleable.Window_windowActionBarFullscreenDecorLayout,
  246. R.layout.screen_action_bar);
  247. } else {
  248. layoutResource = R.layout.screen_title;
  249. }
  250. // System.out.println("Title!");
  251. } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
  252. layoutResource = R.layout.screen_simple_overlay_action_mode;
  253. } else {
  254. // Embedded, so no decoration is needed.
  255. layoutResource = R.layout.screen_simple;
  256. // System.out.println("Simple!");
  257. }
  258. mDecor.startChanging();
  259. mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
  260. ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
  261. if (contentParent == null) {
  262. throw new RuntimeException("Window couldn't find content container view");
  263. }
  264. if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
  265. ProgressBar progress = getCircularProgressBar(false);
  266. if (progress != null) {
  267. progress.setIndeterminate(true);
  268. }
  269. }
  270. if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
  271. registerSwipeCallbacks(contentParent);
  272. }
  273. // Remaining setup -- of background and title -- that only applies
  274. // to top-level windows.
  275. if (getContainer() == null) {
  276. final Drawable background;
  277. if (mBackgroundResource != 0) {
  278. background = getContext().getDrawable(mBackgroundResource);
  279. } else {
  280. background = mBackgroundDrawable;
  281. }
  282. mDecor.setWindowBackground(background);
  283. final Drawable frame;
  284. if (mFrameResource != 0) {
  285. frame = getContext().getDrawable(mFrameResource);
  286. } else {
  287. frame = null;
  288. }
  289. mDecor.setWindowFrame(frame);
  290. mDecor.setElevation(mElevation);
  291. mDecor.setClipToOutline(mClipToOutline);
  292. if (mTitle != null) {
  293. setTitle(mTitle);
  294. }
  295. if (mTitleColor == 0) {
  296. mTitleColor = mTextColor;
  297. }
  298. setTitleColor(mTitleColor);
  299. }
  300. mDecor.finishChanging();
  301. return contentParent;
  302. }

我们主要看标注了红色字体的代码,在代码的最后它返回了contentParent,也就是我们所说的R.id.content那个根视图。

在代码的开始部分,有好几段类似以下这样的代码:

if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) { requestFeature(FEATURE_NO_TITLE); }

这行代码的作用是读取Windows风格属性,比如NoTitle, 然后调用requestFeature(FEATURE_NO_TITLE),想必这个requestFeature函数大家都用过吧,比如我们想用代码来实现全屏无标题,需要在setContentView之前调用这个函数。代码看到这里,我们的setContentView还没有调用完毕,这也就是说requestFeature必须在写在Activity的setContentView之前

然后值得注意的是 layoutResource = R.layout.screen_simple; 这行代码,有几处都是根据features的值来加载相应的布局。

最终调用mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);把这个layoutResource添加到了Dectorview里面。在这里我们就会提出疑问,根据前面那个View结构图,不是说Dectorview下面直接包含的是R.id.content视图吗?这正是我们要完善的部分。我们先来看看 layoutResource = R.layout.screen_simple这种情况下screen_title布局里到底包含什么,从SDK源码中我们找到了screen_title布局的XML代码如下:


  
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:layout_width="match_parent"
  3. android:layout_height="match_parent"
  4. android:fitsSystemWindows="true"
  5. android:orientation="vertical">
  6. <ViewStub android:id="@+id/action_mode_bar_stub"
  7. android:inflatedId="@+id/action_mode_bar"
  8. android:layout="@layout/action_mode_bar"
  9. android:layout_width="match_parent"
  10. android:layout_height="wrap_content"
  11. android:theme="?attr/actionBarTheme" />
  12. <FrameLayout
  13. android:id="@android:id/content"
  14. android:layout_width="match_parent"
  15. android:layout_height="match_parent"
  16. android:foregroundInsidePadding="false"
  17. android:foregroundGravity="fill_horizontal|top"
  18. android:foregrou

 

哇,看到那个id/content的FrameLayout了吗?它正是我们所说的R.id.content视图,那我们的View结构图的大方向没有错!只是上面多了个ViewStub,这个ViewStub用来加载不同的标题栏,是一个可以被多种标题布局替换的一个块布局。Ok,那我们现在就来完善一下我们上面的View结构图:

     就是DectorView下面多了一层布局simple_title,然后R.id.content是包含在simple_title里面的。

我们现在总结一下,Activity的布局结构是这样的  PhoneWindow显示----DecotorView-----R.layout.simple_title----R.id.content----R.layout.main(我们自己的布局)。今天就先分析Activity的布局结构到这里,改天再看看这些布局是如何绘制到PhoneWindow上去的(也就是说绘制到Activity上)。

 

 

 

文章来源: blog.csdn.net,作者:冉航--小虾米,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/gaoxiaoweiandy/article/details/95216719

(完)