setContentView()
在Activity创建对应的布局,是怎样的工作流程?
以Android32
源码进行分析
//有三个不同的重载函数
public abstract void setContentView(@LayoutRes int resId);
public abstract void setContentView(View v);
public abstract void setContentView(View v, ViewGroup.LayoutParams lp);
俩个父类的区别
setContentView
在继承不同的Activity
,其内部的源码有所不同,其本质都是一样,只不过AppCompatActivity
是高版本兼容性,在Activity setContentView
上进用了装饰者模式,更好的适应新版本,先以Activity
分析 ,再分析AppCompatActivity
就如鱼得水
继承Activity
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); //跳入 PhoneWind.java
}
}
继承AppCompatActivity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); //跳入 AppCompatDelegateImpl.java
}
}
继承自 Activity 的 setContentView
Activity.java
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID); //通过getWindow()调用
initWindowDecorActionBar();
}
Window
这里的getWindow
是通过抽象类Window
,调用了抽象函数 setContentView(@LayoutRes int resId)
那么具体实现是在PhoneWind.Java
中 ,其PhoneWind
是wind
的子类,且实现了父类中的抽象函数
public abstract class Window {
public abstract void setContentView(@LayoutRes int layoutResID);
//...
}
public class PhoneWindow extends Window implements MenuBuilder.Callback {
@Override
public void setContentView(int layoutResID) {
//下面有具体分析
}
//...
}
PhoneWind类
PhoneWind
是 抽象Window
的子类,那么PhoneWind
是什么时候创建的呢?
PhoneWind创建流程:
-
ActivityThread.performLaunchActivity()
-->activity.attach
-
Activity.attach()
-->new PhoneWindow
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
//....
//启动创建一个Activity
activity = mInstrumentation.newActivity(mClassLoader, component.getClassName(), r.intent);
//通过启动activity.attach()
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback,
r.assistToken, r.shareableActivityToken);
//....
}
final void attach(Context context,....) {
//省略
mWindow = new PhoneWindow(this, window, activityConfigCallback); //这里进行New
}
通过ActivityThread.java
内的performLaunchActivity()
通过newActivity()
反射启动对应的actiivty
在调用基类中的Activity中的attach()
,最终PhoneWind
被new
出来
简单回忆一下启动调用流程,继续回到PhoneWindow.java
...
PhoneWindow.java
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor(); //1.重点❤
} 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); //2.重点❤
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true; //3.标识作用❤
}
重点解析
- 其中主要目的创建
DecorView
拿到mContentParent
- 通过
Activity的 R.layout.activity.xml
渲染到mContentParent
进行布局合成【参考下底部合成图】 - 标识状态,在
requestWindowFeature(int featureId)
必须设置在setContentView()
前才有效,窗口的Title,NoActionBar
基本属性
installDecor()
- 创建
DecorView
拿到mContentParent
,带着疑问在源码中进行查看
private void installDecor() {
mForceDecorInstall = false;
// 创建 DecorView 并绑定当前Window
if (mDecor == null) {
//创建 DecorView
mDecor = generateDecor(-1); //1.重点❤
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
//拿到DecorView与PhoneWind进行绑定
mDecor.setWindow(this);//2.重点❤
}
if (mContentParent == null) {
//创建 mContentParent,拿到该对象
mContentParent = generateLayout(mDecor); //3.重点❤
// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeFrameworkOptionalFitsSystemWindows();
//....
}
//根据配置 设置 DecorView
//...
if (hasFeature(FEATURE_ACTIVITY_TRANSITIONS)) {
//创建TransitionManager 来管理过渡配置
...
}
-
generateDecor(-1)
当DecorView
为null
的时候需要创建DecorView
- 把
PhoneWind
与DecorView
进行绑定 - 创建拿到
mContentParent = generateLayout(mDecor)
generateDecor(-1)
protected DecorView generateDecor(int featureId) {
// System process doesn't have application context and in that case we need to directly use
// the context we have. Otherwise we want the application context, so we don't cling to the
// activity.
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, this);
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
//重点❤, 直接new DecorView 返回对象
return new DecorView(context, featureId, this, getAttributes());
}
mDecor.setWindow(this)
void setWindow(PhoneWindow phoneWindow) {
mWindow = phoneWindow;
Context context = getContext();
if (context instanceof DecorContext) {
DecorContext decorContext = (DecorContext) context;
//重点❤,DecorView.setPhoneWind(PhoneWindow) 绑定
decorContext.setPhoneWindow(mWindow);
}
if (mPendingWindowBackground != null) {
Drawable background = mPendingWindowBackground;
mPendingWindowBackground = null;
setWindowBackground(background);
}
}
mContentParent = generateLayout(mDecor)
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
//....
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
//重点❤,可以关注该函数内部有标识 常量 【mContentParentExplicitlySet = true; //3.标识作用❤】
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);
}
//重点❤,拿到layoutResource 根据IDE创建xml选择不同创建的样式
int layoutResource;
if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
//默认为 R.layout.screen_simple.xml
layoutResource = R.layout.screen_simple;
}
mDecor.startChanging();
//重点❤ 内部会通过 inflate, 并且add到DecorView中的R.id.content位置 【后面分析】
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
return contentParent;
}
R.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>
会拿到android:id/content
ID控件,把当初activity xml
进行添加到这里形成新的mDecorCaptionView
onResourcesLoaded(mLayoutInflater, layoutResource)
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
if (mBackdropFrameRenderer != null) {
loadBackgroundDrawablesIfNeeded();
mBackdropFrameRenderer.onResourcesLoaded(
this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
getCurrentColor(mNavigationColorViewState));
}
mDecorCaptionView = createDecorCaptionView(inflater);
//重点❤ 通过拿到模板【R.layout.screen_simple】解析成View
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 {
// 重点❤ 通过addView添加到DecorView
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
initializeElevation();
}
继承Activity流程图
SetContentView继承Activity流程图.png继承自 AppCompatActivity 的 setContentView
setContentView()
@Override
public void setContentView(@LayoutRes int layoutResID) {
initViewTreeOwners();
//重点❤
getDelegate().setContentView(layoutResID);
}
@Override
public void setContentView(View view) {
initViewTreeOwners();
getDelegate().setContentView(view);
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
initViewTreeOwners();
getDelegate().setContentView(view, params);
}
getDelegate()
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
//创建Delegate
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
public static AppCompatDelegate create(@NonNull Activity activity,
@Nullable AppCompatCallback callback) {
return new AppCompatDelegateImpl(activity, callback);
}
核心 setContentView
@Override
public void setContentView(int resId) {
//1.重点❤ 创建Decor 具体细分
ensureSubDecor();
//拿到模板资源 android.R.id.content
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
//移除所有View
contentParent.removeAllViews();
//2.重点❤ 替换 把 activity xml 进行添加到 contentParent中
LayoutInflater.from(mContext).inflate(resId, contentParent);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
ensureSubDecor
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
//1.1 重点❤ 创建SubDecor , 其实和Activity中的DecorView一样,命名不同 这里是 SubDecor 容器是ViewGroup对象
mSubDecor = createSubDecor();
// If a title was set before we installed the decor, propagate it now
CharSequence title = getTitle();
if (!TextUtils.isEmpty(title)) {
if (mDecorContentParent != null) {
mDecorContentParent.setWindowTitle(title);
} else if (peekSupportActionBar() != null) {
peekSupportActionBar().setWindowTitle(title);
} else if (mTitleView != null) {
mTitleView.setText(title);
}
}
this.ensureWindow();
this.mWindow.getDecorView();
LayoutInflater inflater = LayoutInflater.from(this.mContext);
ViewGroup subDecor = null;
//1.2 重点❤ 标识
mSubDecorInstalled = true;
// Invalidate if the panel menu hasn't been created before this.
// Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
// being called in the middle of onCreate or similar.
// A pending invalidation will typically be resolved before the posted message
// would run normally in order to satisfy instance state restoration.
PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
if (!mIsDestroyed && (st == null || st.menu == null)) {
invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
}
}
}
createSubDecor
private ViewGroup createSubDecor() {
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
// Style Theme 的包为AppCompatTheme
if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
a.recycle();
throw new IllegalStateException(
"You need to use a Theme.AppCompat theme (or descendant) with this activity.");
}
//...
//重点❤ 创建DecorView 拿到mContentParent【和前面一样 】
ensureWindow();
mWindow.getDecorView();
final LayoutInflater inflater = LayoutInflater.from(mContext);
//重点❤ 变量 subDecor
ViewGroup subDecor = null;
//...
if (mOverlayActionMode) {
subDecor = (ViewGroup) inflater.inflate(
R.layout.abc_screen_simple_overlay_action_mode, null);
} else {
subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
}
//...
ViewUtils.makeOptionalFitsSystemWindows(subDecor);
//重点❤ 拿到R.layout.abc_screen_simple 内部的view 【subDecor】
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
//重点❤ 拿的是Activity里面R.layout.screen_simple内部的View 【ViewDecor】
final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
if (windowContentView != null) {
//把R.layout.screen_simple 中的Content复制到 R.layout.abc_screen_simple
while (windowContentView.getChildCount() > 0) {
final View child = windowContentView.getChildAt(0);
windowContentView.removeViewAt(0);
contentView.addView(child);
}
//VideDecor 的id设置null
windowContentView.setId(View.NO_ID);
//subDecor的id,设置成content
contentView.setId(android.R.id.content);
}
//wubDecor放到phonewind里面去
mWindow.setContentView(subDecor);
contentView.setAttachListener(new ContentFrameLayout.OnAttachListener() {
@Override
public void onAttachedFromWindow() {}
@Override
public void onDetachedFromWindow() {
dismissPopups();
}
});
return subDecor;
}
R.layout.abc_screen_simple
<androidx.appcompat.widget.FitWindowsLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/action_bar_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:fitsSystemWindows="true">
<androidx.appcompat.widget.ViewStubCompat
android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/abc_action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<include layout="@layout/abc_screen_content_include" />
</androidx.appcompat.widget.FitWindowsLinearLayout>
R.layout.abc_screen_content_include
<!-- 注意是 merge -->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.appcompat.widget.ContentFrameLayout
android:id="@id/action_bar_activity_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</merge>
ensureWindow();
会拿到Activity
里面的基本布局R.layout.screen_simple
,拿到ViewDecor
和mContentParent
再用新变量subDecor
拿到R.layout.abc_screen_simple
内部的@id/action_bar_activity_content
在通过替换,把subDecor
布局的id
进行修改 ,viewDecor
的id设置成NO_ID
渲染布局 mLayoutInflater.inflate(layoutResID, mContentParent)
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
XmlResourceParser parser = res.getLayout(resource);
try {
//重点❤ XmlResourceParser解析
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
inflate
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
try {
advanceToRootNode(parser);
final String name = parser.getName();
if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
//merge 标签
if (TAG_MERGE.equals(name)) {
//root==null attachToRoot=false 会报错
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
//重点❤ 通过反射创建View --- 布局的rootView
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
// 当root!=null || attachToRoot=false
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
//重点❤
rInflateChildren(parser, temp, attrs, true);
if (DEBUG) {
System.out.println("-----> done inflating children");
}
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// 当root==null || attachToRoot=false
if (root == null || !attachToRoot=false) {
result = temp;
}
}
} catch (XmlPullParserException e) {
final InflateException ie = new InflateException(e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(
getParserStateDescription(inflaterContext, attrs)
+ ": " + e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return result;
}
}
createViewFromTag
@UnsupportedAppUsage
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
// Apply a theme wrapper, if allowed and one is specified.
if (!ignoreThemeAttr) {
final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
final int themeResId = ta.getResourceId(0, 0);
if (themeResId != 0) {
context = new ContextThemeWrapper(context, themeResId);
}
ta.recycle();
}
try {
View view = tryCreateView(parent, name, context, attrs);
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
//重点❤ 判断是否是自定义布局
if (-1 == name.indexOf('.')) {
view = onCreateView(context, parent, name, attrs);
} else {
view = createView(context, name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (InflateException e) {
throw e;
} catch (ClassNotFoundException e) {
final InflateException ie = new InflateException(
getParserStateDescription(context, attrs)
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(
getParserStateDescription(context, attrs)
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
}
}
if (-1 == name.indexOf('.'))
判断是否控件包名带有.
依据为自定义控件
onCreateView(context, parent, name, attrs)
通过自定义构造出具体得view,通过PhoneLayoutInflater.onCreateView(name, attrs);
PhoneLayoutInflater.java
@Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
for (String prefix : sClassPrefixList) {
try {
//内部继续调用 view = createView(context, name, null, attrs);
View view = createView(name, prefix, attrs);
if (view != null) {
return view;
}
} catch (ClassNotFoundException e) {
// In this case we want to let the base class take a crack
// at it.
}
}
return super.onCreateView(name, attrs);
}
createView
@Nullable
public final View createView(@NonNull Context viewContext, @NonNull String name,
@Nullable String prefix, @Nullable AttributeSet attrs)
throws ClassNotFoundException, InflateException {
//....
Class<? extends View> clazz = null;
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
if (constructor == null) {
//重点❤ 反射获取View
clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);
if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, viewContext, attrs);
}
}
//重点❤ 对应的构造函数
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
sConstructorMap.put(name, constructor);
}
Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = viewContext;
Object[] args = mConstructorArgs;
args[1] = attrs;
try {
//重点❤ 创建具体得view
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
//特殊的viewWtub处理
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
return view;
} finally {
mConstructorArgs[0] = lastContext;
}
}
//....
}
rInflate
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
boolean pendingRequestFocus = false;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {
pendingRequestFocus = true;
consumeChildElements(parser);
} else if (TAG_TAG.equals(name)) {
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
} else {
//重点❤, createViewFromTag 创建对应的View 上面有分析
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
}
}
if (pendingRequestFocus) {
parent.restoreDefaultFocus();
}
if (finishInflate) {
parent.onFinishInflate();
}
}
inflate参数
View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
-
resource
:需要加载布局的文件id,将这个布局文件加载出UI会话
中来 -
root
:需要附加到resource
资源文件的根控件,inflate
会返回一个view
对象,会根据第三个参数attachToRoot
影响-
attachToRoot=True
,就是将root
作为根对象返回 -
attachToRoot=False
,仅仅将这个root
对象的LayoutParams
属性返回到resource
对象的根布局上,相当于没有根
-
-
attachToRoot
:是否将root
附加到布局文件的根视图上
LayoutInflate的参数的作用
// 方式一:将布局添加成功
View view = inflater.inflate(R.layout.inflate_layout, layoutRoot, true);
// 方式二:报错,一个View只能有一个父亲(The specified child already has a parent.)
View view = inflater.inflate(R.layout.inflate_layout, layoutRoot, true); // 已经addView
ll.addView(view);
// 方式三:布局成功,第三个参数为false
// 目的:想要 inflate_layout 的根节点的属性(宽高)有效,又不想让其处于某一个容器中
View view = inflater.inflate(R.layout.inflate_layout, layoutRoot false);
ll.addView(view);
// 方式四:root = null,这个时候不管第三个参数是什么,显示效果一样
// inflate_layout 的根节点的属性(宽高)设置无效,只是包裹子View,
// 但是子View(Button)有效,因为Button是出于容器下的
View view = inflater.inflate(R.layout.inflate_layout, null, false);
ll.addView(view);
merge
- 优化布局
- 必须作为
rootView
根
include
-
Id
的特殊性,引用布局设置了Id
,内部的rootView
便无效,反之! -
不能作为
rootView
根根元素,需要放在ViewGroup
中
ViewStub
- 根
include
特性基础一致 - 其作用隐藏,懒加载
网友评论