从setContentView看Activity的构成
来源:互联网 发布:淘宝店铺注册单可靠吗 编辑:程序博客网 时间:2024/05/29 06:30
常规地,我们会在Activity的onCreate()方法内调用setContentView(R.layout.xxx)以此来设置页面布局。那么,这个方法背后到底隐藏了什么操作呢?PS:以下源码是参照API 25
首先,我们先进入Activity#setContentView(int layoutResID)
public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); } public Window getWindow() { return mWindow; }
然后我们可以发现,实际是调用了getWindow()的setContentView(),而getWindow()返回的是成员变量mWindow,mWindow是Window类型,是个抽象类。而mWindow在Activity#attach时被赋值:
mWindow = new PhoneWindow(this, window);
所以实际调用的是PhoneWindow#setContentView(),进入其中:
public void setContentView(int layoutResID) { if (mContentParent == null) { installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene); } else { mLayoutInflater.inflate(layoutResID, mContentParent); } mContentParent.requestApplyInsets(); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } mContentParentExplicitlySet = true; }
从第13行代码看出:从外部传进来的layoutResID会被LayoutInflater inflate的,然后被添加到mContentParent里。接下来我们一起探究mContentParent是什么妖怪吧。
// This is the view in which the window contents are placed. It is either mDecor itself, or a child of mDecor where the contents go. ViewGroup mContentParent;
注释上说,这个View是用来放window的内容的,可以是mDecor或者是mDecor的子View。而这个mDecor其实就是DecorView,而DecorView的解释是:
// This is the top-level view of the window, containing the window decor. private DecorView mDecor;
它是Window最上层的View,它包含了Window的装饰。
扯远了扯远了,我们回过头来看刚才的PhoneWindow#setContentView(),从上往下看,当mContentParent == null时,调用installDecor():
private void installDecor() { mForceDecorInstall = false; if (mDecor == null) { //实例化DecorView mDecor = generateDecor(-1); mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); mDecor.setIsRootNamespace(true); if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); } } else { mDecor.setWindow(this); } if (mContentParent == null) { //生成mContentParent mContentParent = generateLayout(mDecor); ... } protected DecorView generateDecor(int featureId) { ... return new DecorView(context, featureId, this, getAttributes()); }
判断mDecor为空则new一个DecorView,并且把当前Window实例传给它。然后mContentParent为空则把DecorView传进generateLayout()里生成一个mContentParent。generateLayout这个方法巨长,省略一些不太重要的代码片段:
protected ViewGroup generateLayout(DecorView decor) { ... // Inflate the window decor. int layoutResource; int features = getLocalFeatures(); // System.out.println("Features: 0x" + Integer.toHexString(features)); if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) { layoutResource = R.layout.screen_swipe_dismiss; } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) { if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( R.attr.dialogTitleIconsDecorLayout, res, true); layoutResource = res.resourceId; } else { layoutResource = R.layout.screen_title_icons; } // XXX Remove this once action bar supports these features. removeFeature(FEATURE_ACTION_BAR); // System.out.println("Title Icons!"); } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0 && (features & (1 << FEATURE_ACTION_BAR)) == 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 = 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) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( R.attr.dialogCustomTitleDecorLayout, res, true); layoutResource = res.resourceId; } else { layoutResource = R.layout.screen_custom_title; } // XXX Remove this once action bar supports these features. removeFeature(FEATURE_ACTION_BAR); } 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) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( R.attr.dialogTitleDecorLayout, res, true); layoutResource = res.resourceId; } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) { layoutResource = a.getResourceId( R.styleable.Window_windowActionBarFullscreenDecorLayout, R.layout.screen_action_bar); } else { layoutResource = R.layout.screen_title; } // System.out.println("Title!"); } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) { layoutResource = R.layout.screen_simple_overlay_action_mode; } else { // Embedded, so no decoration is needed. layoutResource = R.layout.screen_simple; // System.out.println("Simple!"); } mDecor.startChanging(); mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); if (contentParent == null) { throw new RuntimeException("Window couldn't find content container view"); } ... mDecor.finishChanging(); return contentParent;}
前面一大段都在根据FEATURE值来决定到底是要用哪个layout文件,也就是layoutResource的值。然后调用mDecor.onResourcesLoaded()
方法,再通过findViewById(ID_ANDROID_CONTENT)
找到contentParent最后返回,赋值给PhoneWindow里的mContentParent。
从前面一大段if else看出,layoutResource可以是layout.screen_title_icons,layout.screen_custom_title,layout.screen_title等。打开这些layout,可以发现都存在一个id为”@android:id/content”的FrameLayout。以layout.screen_simple为例:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" android:orientation="vertical"> <ViewStub android:id="@+id/action_mode_bar_stub" android:inflatedId="@+id/action_mode_bar" android:layout="@layout/action_mode_bar" android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="?attr/actionBarTheme" /> <FrameLayout android:id="@android:id/content" android:layout_width="match_parent" android:layout_height="match_parent" android:foregroundInsidePadding="false" android:foregroundGravity="fill_horizontal|top" android:foreground="?android:attr/windowContentOverlay" /></LinearLayout>
而ID_ANDROID_CONTENT的值其实就是
/** * The ID that the main layout in the XML layout file should have. */ public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
刚好能在上述layout里找到,所以能find到,是不是很magic~。既然能find到,说明肯定有把layoutResource inflate出来再add。
我们再进入mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
看一看。注意这里是调用了DecorView里的方法,所以接下来的addView都是add到DecorView里
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) { ... mDecorCaptionView = createDecorCaptionView(inflater); final View root = inflater.inflate(layoutResource, null); if (mDecorCaptionView != null) { if (mDecorCaptionView.getParent() == null) { addView(mDecorCaptionView, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); } mDecorCaptionView.addView(root, new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT)); } else { // Put it below the color views. addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); } mContentRoot = (ViewGroup) root; initializeElevation(); }
可以看到,会生成mDecorCaptionView和root。如果mDecorCaptionView不为空,则把它加入到DecorView里,然后再把root加入到它里面;否则,直接把root加入到DecorView里。
总结
Activity里有phoneWindow,phoneWindow内有decorView,decorView里子View有两种情况:
1. 先是mDecorCaptionView,往里一级才是layoutResource inflate出来的root布局
2. 直接是root布局
再然后,root里一定包含id为”@android:id/content”的FrameLayout,FrameLayout里才是我们通过setContentView传进去的布局
- 从setContentView看Activity的构成
- Activity的setContentView看View的绘制流程
- 从源码的角度说说Activity的setContentView的原理
- Activity的setContentView解析
- 从Activity setContentView了解LayoutInflater(一)
- Activity 的 setContentView draw时机
- Activity中的Window的setContentView
- Activity的setContentView源码分析
- 学习android Activity的setContentView
- Activity的构成
- 从源码的角度说说Activity的setContentView的原理(二)
- 源码分析Activity的构成
- activity的setContentView的另一种写法
- Activity的setContentView渲染的原理
- 切换Activity中布局的setContentView( )方法
- android 通过setContentView切换Activity的View
- 谈谈activity中setContentView()内部的实现。
- Activity的setContentView()方法源码分析
- Android项目中接入微信第三方支付及一些问题
- java自定义标签-新增xsd
- Python3 对列表按元组指定列进行排序
- https证书生成过程
- maven 项目,运行main方法时java找不到或无法加载主类
- 从setContentView看Activity的构成
- 每日一练27
- 深入理解javascript原型和闭包(5)——instanceof
- redis
- HTTP长连接与短连接
- 基于Linux操作系统
- JAVA反射_代理
- Android加密之文件级加密
- 客户端HttpClient4处理 Servlet Gzip后的内容