大家好,又见面了,我是你们的朋友全栈君。
更新于:2022-01-09 Android-30 implementation ‘androidx.appcompat:appcompat:1.2.0’ setContentView并没有将view添加到屏幕上,只是创建了DecorView,xml添加到DecorView而已。
import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
/** * 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. * * @see #setContentView(android.view.View) * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams) */
public void setContentView(@LayoutRes int layoutResID) {
// getWindow() 是 抽象类Window对象,其子类为 PhoneWindow
// 这里调用的是 PhoneWindow的setContentView
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
@Override
public void setContentView(int layoutResID) {
// 初始化DecorView,并对 mContentParent 赋值 mContentParent就是一个View
// mContentParent 是 R.layout.screen_simple资源文件 中 FramLayout 这个View
// 后面会提到这里
if (mContentParent == null) {
installDecor();
}
......
// 将 layoutResID 即 R.layout.activity_main 填入到 mContentParent,渲染操作
mLayoutInflater.inflate(layoutResID, mContentParent);
......
// 设置标志位
// 如:当调用requsetWindowFeature时,如果在setContentView之后调用
// 就会抛出异常,因为这里设置了值
mContentParentExplicitlySet = true;
}
private void installDecor() {
......
// This is the top-level view of the window, containing the window decor.
// mDecor 是一个 DecorView 即顶层的 View,它包含 window
mDecor = generateDecor(-1);
......
// 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.
// mContentParent 是 ViewGroup 即 它是用来放置view的
// 要么是mDecor本身,要么是mDecor的子代
mContentParent = generateLayout(mDecor);
......
}
protected DecorView generateDecor(int featureId) {
......
// 初始化DecorView
return new DecorView(context, featureId, this, getAttributes());
}
初始化 mContentParent 根据不同的主题选择不同的资源文件。
protected ViewGroup generateLayout(DecorView decor) {
......
// 根据不同的主题,选择不同的资源文件
// 这里以 R.layout.screen_simple 文件为例说明
layoutResource = R.layout.screen_simple;
......
// 将 R.layout.screen_simple 添加到 DecorView 即顶层的 View
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
// 获取 id 为 ID_ANDROID_CONTENT 即 com.android.internal.R.id.content
// contentParent 就是 R.layout.screen_simple资源文件 中 FramLayout 这个View
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
......
return contentParent;
}
<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>
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
......
// 加载资源文件 如上方提到的 R.layout.screen_simple
final View root = inflater.inflate(layoutResource, null);
......
// 将 资源文件 加载到 DecorView 中
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
......
}
渲染操作
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root,
boolean attachToRoot) {
......
return inflate(parser, root, attachToRoot);
......
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root,
boolean attachToRoot) {
......
// 通过反射创建View
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
......
ViewGroup.LayoutParams params = null;
......
params = root.generateLayoutParams(attrs);
......
// 创建子View
rInflateChildren(parser, temp, attrs, true);
......
root.addView(temp, params);
......
}
private View createViewFromTag(View parent, String name, Context context,
AttributeSet attrs) {
return createViewFromTag(parent, name, context, attrs, false);
}
View createViewFromTag(View parent, String name, Context context,
AttributeSet attrs, boolean ignoreThemeAttr) {
......
// 创建view
View view = tryCreateView(parent, name, context, attrs);
// 如果 view 为null,使用默认的创建 view 的方式创建
// 也就是通过 AppCompatDelegateImpl 来创建 View
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
// 判断是否为sdk自己的
if (-1 == name.indexOf('.')) {
view = onCreateView(context, parent, name, attrs);
} else {
view = createView(context, name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
}
创建View,当 mFactory2 不为空,就用 factory2 来创建view,否则就返回 view为null
当继承Activity时,并没有默认设置 factory2,当继承 AppCompatActivity 时,在 super.onCreate(savedInstanceState) 进行了设置 factory2
public final View tryCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context,
@NonNull AttributeSet attrs) {
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);
}
View view;
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
return view;
}
@Override
public final View onCreateView(View parent, String name,
Context context, AttributeSet attrs) {
return createView(parent, name, context, attrs);
}
从代码看,是调用 AppCompatViewInflater的createView方法
@Override
public View createView(View parent, final String name,
@NonNull Context context, @NonNull AttributeSet attrs) {
......
mAppCompatViewInflater = new AppCompatViewInflater();
......
return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
IS_PRE_LOLLIPOP,
true,
VectorEnabledTintResources.shouldBeUsed()
);
}
从下方代码可以看出,当创建的TextView等时,会进行替换操作,如TextView替换为AppCompatTextView
final View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
......
View view = null;
switch (name) {
case "TextView":
view = createTextView(context, attrs);
verifyNotNull(view, name);
break;
case "ImageView":
view = createImageView(context, attrs);
verifyNotNull(view, name);
break;
case "Button":
view = createButton(context, attrs);
verifyNotNull(view, name);
break;
case "EditText":
view = createEditText(context, attrs);
verifyNotNull(view, name);
break;
case "Spinner":
view = createSpinner(context, attrs);
verifyNotNull(view, name);
break;
case "ImageButton":
view = createImageButton(context, attrs);
verifyNotNull(view, name);
break;
case "CheckBox":
view = createCheckBox(context, attrs);
verifyNotNull(view, name);
break;
case "RadioButton":
view = createRadioButton(context, attrs);
verifyNotNull(view, name);
break;
case "CheckedTextView":
view = createCheckedTextView(context, attrs);
verifyNotNull(view, name);
break;
case "AutoCompleteTextView":
view = createAutoCompleteTextView(context, attrs);
verifyNotNull(view, name);
break;
case "MultiAutoCompleteTextView":
view = createMultiAutoCompleteTextView(context, attrs);
verifyNotNull(view, name);
break;
case "RatingBar":
view = createRatingBar(context, attrs);
verifyNotNull(view, name);
break;
case "SeekBar":
view = createSeekBar(context, attrs);
verifyNotNull(view, name);
break;
case "ToggleButton":
view = createToggleButton(context, attrs);
verifyNotNull(view, name);
break;
default:
view = createView(context, name, attrs);
}
......
return view;
}
当为 TextView 的时候,就会被替换为 AppCompatTextView
@NonNull
protected AppCompatTextView createTextView(Context context,
AttributeSet attrs) {
return new AppCompatTextView(context, attrs);
}
public View onCreateView(@NonNull Context viewContext, @Nullable View parent,
@NonNull String name, @Nullable AttributeSet attrs)
throws ClassNotFoundException {
return onCreateView(parent, name, attrs);
}
protected View onCreateView(View parent, String name, AttributeSet attrs)
throws ClassNotFoundException {
// 这里是两个参数的,调用的是 onCreateView 的 onCreateView,进行重写了
return onCreateView(name, attrs);
}
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
@Override protected View onCreateView(String name, AttributeSet attrs)
throws ClassNotFoundException {
for (String prefix : sClassPrefixList) {
try {
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);
}
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Context context = (Context) mConstructorArgs[0];
if (context == null) {
context = mContext;
}
return createView(context, name, prefix, attrs);
}
// 通过反射创建View
public final View createView(@NonNull Context viewContext, @NonNull String name,
@Nullable String prefix, @Nullable AttributeSet attrs)
throws ClassNotFoundException, InflateException {
clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);
constructor = clazz.getConstructor(mConstructorSignature);
final View view = constructor.newInstance(args);
}
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 在 setContentView 之前调用
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
// error
// requestWindowFeature(Window.FEATURE_NO_TITLE);
}
}
报错内容:
查看requestWindowFeature的代码逻辑:
调用的是PhoneWindow的requestFeature方法,里面会判断mContentParentExplicitlySet是否已经设置过值了,如果为true,就会报错!
public boolean requestFeature(int featureId) {
if (mContentParentExplicitlySet) {
throw new AndroidRuntimeException(
"requestFeature() must be called before adding content");
}
......
}
其实,在PhoneWindow#setContentView方法的最后一行,会设置 mContentParentExplicitlySet 为ture,所以在之后调用,就会报错了。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
在onCreate中,会设置factory,去看 installViewFactory
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
final AppCompatDelegate delegate = getDelegate();
delegate.installViewFactory();
delegate.onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
}
进行设置 Factory2
如:当创建 TextView 的时候,会因为这里设置了 Factory2,而把 TextView 替换为 AppCompatTextView
@Override
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
if (layoutInflater.getFactory() == null) {
LayoutInflaterCompat.setFactory2(layoutInflater, this);
} else {
if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
+ " so we can not install AppCompat's");
}
}
}
调用的是AppCompatDelegateImpl实现类中的 setContentView
@Override
public void setContentView(@LayoutRes int layoutResID) {
initViewTreeOwners();
getDelegate().setContentView(layoutResID);
}
@Override
public void setContentView(int resId) {
// 确保ActionBar的特有UI结构构建完毕
ensureSubDecor();
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
// 确保ContentView的所有Child全部被移除干净
contentParent.removeAllViews();
// 将画面的内容布局解析并添加到ContentView下
LayoutInflater.from(mContext).inflate(resId, contentParent);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
private ViewGroup createSubDecor() {
.....
mWindow.getDecorView(); // 同MainActivity继承Activity的逻辑,看那个就行
......
subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
final ContentFrameLayout contentView = (ContentFrameLayout)
subDecor.findViewById(R.id.action_bar_activity_content);
final ViewGroup windowContentView = (ViewGroup)
mWindow.findViewById(android.R.id.content);
......
if (windowContentView != null) {
// There might be Views already added to the Window's content view
// so we need to
// migrate them to our content view
while (windowContentView.getChildCount() > 0) {
final View child = windowContentView.getChildAt(0);
windowContentView.removeViewAt(0);
contentView.addView(child);
}
// Change our content FrameLayout to use the android.R.id.content id.
// Useful for fragments.
// 把 android.R.id.content 这里,改为 NO_ID
windowContentView.setId(View.NO_ID);
// R.id.action_bar_activity_content 改为 android.R.id.content
// 就是做了下替换操作
contentView.setId(android.R.id.content);
// The decorContent may have a foreground drawable
// set (windowContentOverlay).
// Remove this as we handle it ourselves
if (windowContentView instanceof FrameLayout) {
((FrameLayout) windowContentView).setForeground(null);
}
}
// Now set the Window's content view with the decor
mWindow.setContentView(subDecor);
......
return subDecor;
}
public final @NonNull View getDecorView() {
if (mDecor == null || mForceDecorInstall) {
// 同MainActivity继承Activity的逻辑,看那个就行
installDecor();
}
return mDecor;
}
<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>
<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>
渲染操作
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root,
boolean attachToRoot) {
......
return inflate(parser, root, attachToRoot);
......
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root,
boolean attachToRoot) {
......
// 通过反射创建View
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
......
ViewGroup.LayoutParams params = null;
......
params = root.generateLayoutParams(attrs);
......
// 创建子View
rInflateChildren(parser, temp, attrs, true);
......
root.addView(temp, params);
......
}
private View createViewFromTag(View parent, String name, Context context,
AttributeSet attrs) {
return createViewFromTag(parent, name, context, attrs, false);
}
View createViewFromTag(View parent, String name, Context context,
AttributeSet attrs, boolean ignoreThemeAttr) {
......
// 创建view
View view = tryCreateView(parent, name, context, attrs);
// 如果 view 为null,使用默认的创建 view 的方式创建
// 也就是通过 AppCompatDelegateImpl 来创建 View
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
// 判断是否为sdk自己的
if (-1 == name.indexOf('.')) {
// sdk的
view = onCreateView(context, parent, name, attrs);
} else {
// 自己的,或者封装的
view = createView(context, name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
}
创建View,当 mFactory2 不为空,就用 factory2 来创建view,否则就返回 view为null
public final View tryCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context,
@NonNull AttributeSet attrs) {
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);
}
View view;
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
return view;
}
@Override
public final View onCreateView(View parent, String name,
Context context, AttributeSet attrs) {
return createView(parent, name, context, attrs);
}
从代码看,是调用 AppCompatViewInflater的createView方法
@Override
public View createView(View parent, final String name,
@NonNull Context context, @NonNull AttributeSet attrs) {
......
mAppCompatViewInflater = new AppCompatViewInflater();
......
return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
IS_PRE_LOLLIPOP,
true,
VectorEnabledTintResources.shouldBeUsed()
);
}
从下方代码可以看出,当创建的TextView等时,会进行替换操作,如TextView替换为AppCompatTextView
final View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
......
View view = null;
switch (name) {
case "TextView":
view = createTextView(context, attrs);
verifyNotNull(view, name);
break;
case "ImageView":
view = createImageView(context, attrs);
verifyNotNull(view, name);
break;
case "Button":
view = createButton(context, attrs);
verifyNotNull(view, name);
break;
case "EditText":
view = createEditText(context, attrs);
verifyNotNull(view, name);
break;
case "Spinner":
view = createSpinner(context, attrs);
verifyNotNull(view, name);
break;
case "ImageButton":
view = createImageButton(context, attrs);
verifyNotNull(view, name);
break;
case "CheckBox":
view = createCheckBox(context, attrs);
verifyNotNull(view, name);
break;
case "RadioButton":
view = createRadioButton(context, attrs);
verifyNotNull(view, name);
break;
case "CheckedTextView":
view = createCheckedTextView(context, attrs);
verifyNotNull(view, name);
break;
case "AutoCompleteTextView":
view = createAutoCompleteTextView(context, attrs);
verifyNotNull(view, name);
break;
case "MultiAutoCompleteTextView":
view = createMultiAutoCompleteTextView(context, attrs);
verifyNotNull(view, name);
break;
case "RatingBar":
view = createRatingBar(context, attrs);
verifyNotNull(view, name);
break;
case "SeekBar":
view = createSeekBar(context, attrs);
verifyNotNull(view, name);
break;
case "ToggleButton":
view = createToggleButton(context, attrs);
verifyNotNull(view, name);
break;
default:
view = createView(context, name, attrs);
}
......
return view;
}
当为 TextView 的时候,就会被替换为 AppCompatTextView
@NonNull
protected AppCompatTextView createTextView(Context context,
AttributeSet attrs) {
return new AppCompatTextView(context, attrs);
}
public View onCreateView(@NonNull Context viewContext, @Nullable View parent,
@NonNull String name, @Nullable AttributeSet attrs)
throws ClassNotFoundException {
return onCreateView(parent, name, attrs);
}
protected View onCreateView(View parent, String name, AttributeSet attrs)
throws ClassNotFoundException {
// 这里是两个参数的,调用的是 onCreateView 的 onCreateView,进行重写了
return onCreateView(name, attrs);
}
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
@Override protected View onCreateView(String name, AttributeSet attrs)
throws ClassNotFoundException {
for (String prefix : sClassPrefixList) {
try {
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);
}
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Context context = (Context) mConstructorArgs[0];
if (context == null) {
context = mContext;
}
return createView(context, name, prefix, attrs);
}
// 通过反射创建View
public final View createView(@NonNull Context viewContext, @NonNull String name,
@Nullable String prefix, @Nullable AttributeSet attrs)
throws ClassNotFoundException, InflateException {
clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);
constructor = clazz.getConstructor(mConstructorSignature);
final View view = constructor.newInstance(args);
}
需要使用supportRequestWindowFeature,因为AppCompatActivity类里面会覆盖设置。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// requestWindowFeature(Window.FEATURE_NO_TITLE);
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
}
}
当调用requestWindowFeature,设置的代码如下,其中使用的mLocalFeatures这个:
public boolean requestFeature(int featureId) {
final int flag = 1<<featureId;
mFeatures |= flag;
mLocalFeatures |= mContainer != null ? (flag&~mContainer.mFeatures) : flag;
return (mFeatures&flag) != 0;
}
当调用supportRequestWindowFeature,设置的代码如下,使用的mWindowNoTitle,标志位改变了
@Override
public boolean requestWindowFeature(int featureId) {
featureId = sanitizeWindowFeatureId(featureId);
if (mWindowNoTitle && featureId == FEATURE_SUPPORT_ACTION_BAR) {
return false; // Ignore. No title dominates.
}
if (mHasActionBar && featureId == Window.FEATURE_NO_TITLE) {
// Remove the action bar feature if we have no title.
// No title dominates.
mHasActionBar = false;
}
switch (featureId) {
case FEATURE_SUPPORT_ACTION_BAR:
throwFeatureRequestIfSubDecorInstalled();
mHasActionBar = true;
return true;
case FEATURE_SUPPORT_ACTION_BAR_OVERLAY:
throwFeatureRequestIfSubDecorInstalled();
mOverlayActionBar = true;
return true;
case FEATURE_ACTION_MODE_OVERLAY:
throwFeatureRequestIfSubDecorInstalled();
mOverlayActionMode = true;
return true;
case Window.FEATURE_PROGRESS:
throwFeatureRequestIfSubDecorInstalled();
mFeatureProgress = true;
return true;
case Window.FEATURE_INDETERMINATE_PROGRESS:
throwFeatureRequestIfSubDecorInstalled();
mFeatureIndeterminateProgress = true;
return true;
case Window.FEATURE_NO_TITLE:
throwFeatureRequestIfSubDecorInstalled();
mWindowNoTitle = true;
return true;
}
return mWindow.requestFeature(featureId);
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/test_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="hello world"
/>
</LinearLayout>
public class MainActivity extends AppCompatActivity {
private static final String TAG = "AAAAAAA";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textView1 = findViewById(R.id.test_tv);
Log.d(TAG, "textView1: " + textView1);
TextView textView2 = new TextView(this);
Log.d(TAG, "textView2: " + textView2);
}
}
D/AAAAAAA: textView1: com.google.android.material.textview.MaterialTextView{
d43c607 V.ED..... ......ID 0,0-0,0 #7f080195 app:id/test_tv}
D/AAAAAAA: textView2: android.widget.TextView{
c9ba334 V.ED..... ......ID 0,0-0,0}
当继承 AppCompatActivity 时,Activity的 super.onCreate(savedInstanceState) 中会进行默认设置 factory2,然后在执行 LayoutInflater#createViewFromTag 方法时,其中的 tryCreateView 方法会使用到factory2 当factory2不为空时,就会用factory2去创建View,这个view是把TextView替换为了 AppCompatTextView。
1)当root不为空,attachToRoot为true,会执行一次addView 2)当root不为空,attachToRoot为false,会获取到属性 3)当root为空,attachToRoot为false,会直接return,属性没有获取
public View inflate(XmlPullParser parser, @Nullable ViewGroup root,
boolean attachToRoot) {
......
// Temp is the root view that was found in the xml
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);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// Inflate all children under temp against its context.
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);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;
}
......
return result;
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="200dp"
android:layout_height="200dp"
android:background="@color/teal_200"
android:gravity="center"
android:orientation="vertical">
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/activity_linear_layout">
</LinearLayout>
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.widget.LinearLayout;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
LinearLayout linear_layout = findViewById(R.id.activity_linear_layout);
// 正常
// LayoutInflater.from(this).inflate(R.layout.inflate_layout,
// linear_layout, true);
// 报错
// 当执行inflate的时候,已经addView了,当再次addView调用,会报错,
// 一个View只能有一个parent
// The specified child already has a parent.
// You must call removeView() on the child's parent first.
// View view = LayoutInflater.from(this).inflate(
// R.layout.inflate_layout, linear_layout, true);
// linear_layout.addView(view);
// 正常
// 第三个参数为false,不会去addView,所以,当调用addView的时候,就没什么问题
// View view = LayoutInflater.from(this).inflate(
// R.layout.inflate_layout, linear_layout, false);
// linear_layout.addView(view);
// 能显示,但显示不正常,inflate_layout没有父容器了
// inflate_layout的布局无效,由inflate_layout的内容即button的大小决定
View view = LayoutInflater.from(this).inflate(
R.layout.inflate_layout, null, false);
linear_layout.addView(view);
}
}
正常的:
不带有布局参数的:
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/153268.html原文链接:https://javaforall.cn