setContentView源码分析
2022-02-23 本文已影响0人
w达不溜w
直奔主题setContentView:AppCompatActivity#setContentView
@Override
public void setContentView(@LayoutRes int layoutResID) {
initViewTreeOwners();
//getDelegate()具体实现类是AppCompatDelegateImpl
getDelegate().setContentView(layoutResID);
}
进入AppCompatDelegateImpl#setContentView
@Override
public void setContentView(int resId) {
//检测mSubDecor视图是否已经创建,没有则创建mSubDecor视图,同时将视图添加到Window上
ensureSubDecor();
//获取根布局
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
//将我们的布局添加到根布局上
LayoutInflater.from(mContext).inflate(resId, contentParent);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
查看ensureSubDecor方法
private void ensureSubDecor() {
//...
if (!mSubDecorInstalled) {
//创建mSubDecor视图
mSubDecor = createSubDecor();
mSubDecorInstalled = true;
}
// ...
}
createSubDecor()创建mSubDecor视图
private ViewGroup createSubDecor() {
//获得主体样式
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
//我们自定义主体必须继承AppCompat
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.");
}
//...
// 确认Window上是否已安装DecorView,没有则创建DecorView并添加到Window上
// 分析①:mWindow.getDecorView()
mWindow.getDecorView();
final LayoutInflater inflater = LayoutInflater.from(mContext);
ViewGroup subDecor = null;
//...
// 填充subDecor
subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
//...
//找到id为action_bar_activity_content的contentView
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
//找到PhoneWindow中的ContentView
final ViewGroup windowContentView = (ViewGroup)mWindow.findViewById(android.R.id.content);
if (windowContentView != null) {
// 如果已经有视图添加到窗口,移除并添加到contentView中
while (windowContentView.getChildCount() > 0) {
final View child = windowContentView.getChildAt(0);
windowContentView.removeViewAt(0);
contentView.addView(child);
}
//标记android.R.id.content视图的id为NO_ID
//并将我们布局父容器FrameLayout设置为android.R.id.content
windowContentView.setId(View.NO_ID);
contentView.setId(android.R.id.content);
}
// 将我们添加视图的父视图添加到Window上
mWindow.setContentView(subDecor);
//...
return subDecor;
}
分析①:mWindow.getDecorView() 创建DecorView
@Override
public final @NonNull View getDecorView() {
if (mDecor == null || mForceDecorInstall) {
installDecor();
}
return mDecor;
}
installDecor()具体实现
// This is the top-level view of the window, containing the window decor.
//DecorView是Window的顶层View
private DecorView mDecor;
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 = generateLayout(mDecor);
//...
}
}
通过调用generateDecor创建DecorView
protected DecorView generateDecor(int featureId) {
//...
//new一个DecorView
return new DecorView(context, featureId, this, getAttributes());
}
创建DecorView后,再调用generateLayout
protected ViewGroup generateLayout(DecorView decor) {
TypedArray a = getWindowStyle();
//如果设置了Window_windowNoTitle,则会调用requestFeature(FEATURE_NO_TITLE),其它同理
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
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);
}
//是否设置全屏
if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
}
//...
int layoutResource;
int features = getLocalFeatures();
//根据设置的features来加载对应的布局
if()else if()else{
// screen_simple就是DecorView加载的布局
layoutResource = R.layout.screen_simple;
}
//把布局解析加载到DecorView,用的addView()
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//从系统的layoutResource找一个id是android.R.id.content的FrameLayout
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
return contentParent;
}
generateLayout方法会根据用户设置的主题去设置对应的feature,根据feature来选择加载对应的布局文件,这就是为什么要在setContentView之前调用requestFeature的原因。
看下布局R.layout.screen_simple
<?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: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>
FrameLayout里面id是@android:id/content
,我们setContentView的内容就是要添加到这个FrameLayout中。
LayoutInflater解析xml并创建View实例分析
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
LayoutInflater.from(mContext).inflate(resId, contentParent)
> LayoutInflater#inflate() xml解析
> LayoutInflater#createViewFromTag()
> LayoutInflater#tryCreateView()
> AppCompatDelegateImpl#onCreateView()
> AppCompatDelegateImpl#createView()
> AppCompatViewInflater#createView()
tryCreateView()
public final View tryCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context,
@NonNull AttributeSet attrs) {
View view;
//可以通过反射的方式修改mFactory2,从而实现换肤插件
if (mFactory2 != null) {
//最终调用AppCompatViewInflater中的createView方法
//可以通过自定义mFactory2让其生成用户所需要的控件
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;
}
View最终实例化:
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;
// We need to 'inject' our tint aware Views in place of the standard framework versions
//如果是系统控件,则直接new出来
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;
//...
default:
view = createView(context, name, attrs);
}
if (view == null && originalContext != context) {
// 没有匹配,也就是不是系统控件,到则通过反射创建
view = createViewFromTag(context, name, attrs);
}
//...
return view;
}
createViewFromTag最终会调用createViewByPrefix方法
private View createViewByPrefix(Context context, String name, String prefix)
throws ClassNotFoundException, InflateException {
//先从构造缓存中获取
Constructor<? extends View> constructor = sConstructorMap.get(name);
try {
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
Class<? extends View> clazz = Class.forName(
prefix != null ? (prefix + name) : name,
false,
context.getClassLoader()).asSubclass(View.class);
//利用反射构建一个构造函数
constructor = clazz.getConstructor(sConstructorSignature);
sConstructorMap.put(name, constructor);
}
constructor.setAccessible(true);
//利用反射创建View实例
return constructor.newInstance(mConstructorArgs);
} catch (Exception e) {
// We do not want to catch these, lets return null and let the actual LayoutInflater
// try
return null;
}
}
解析xml去匹配系统的View,匹配到通过new创建实例,没有匹配到(如自定义View)则通过反射去创建实例。