一、前言
在 Android 应用开发的世界里,setContentView
简直是每个开发者都会接触到的办法。它的作用至关重要——担任将视图(View
)或布局(Layout
)展现在屏幕上。虽然这看起来是一个简单直接的操作,但其背面实际上隐藏着 Android
体系中杂乱而精妙的窗口办理和视图渲染机制。最近,在与同行的交流中,我发现我简直将这一块忘得一干二净。因此,我决定从 Activity
的 setContentView
办法入手,重新整理并深入探讨 Android 的窗口办理和视图展现原理,希望能够为咱们带来新的理解和启发。阿弥陀佛。
吨吨吨,喝一杯冰美式。要睡着了。
PS:由于代码中有大量的款式、动画、装备相关的代码,我会挑选性的省掉,假如你想看完好的,我运用的是“appcompact1.3.1”、SDK31。
假如您有任何疑问、对文章写的不满意、发现过错或者有更好的办法,欢迎在评论、私信或邮件中提出,十分感谢您的支持。
二、AppCompatActivity
一般来说你都会如此运用setConentView
:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(mContentLayoutId);
}
当然你也能够在这么运用:
class TestActivity : AppCompatActivity(R.layout.activity_test)
咱们看看AppCompatActivity
的setContentView
藏着什么:
@Override
public void setContentView(@LayoutRes int layoutResID) {
//**
getDelegate().setContentView(layoutResID);
}
能够看到在AppCompatActivity
中,运用的getDelegate
,这看起来像是托付啊!点下去看看:
@NonNull
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
咱们找到了全新的AppCompatDelegate
!官方如是说:
此类表示一个托付,您能够运用该托付将 AppCompat 的支持扩展到任何 Activity.只能 Activity 与一个 AppCompatDelegate 实例链接,因此应保留从 create(Activity, AppCompatCallback) 回来的实例,直到 Activity 被销毁。
可是AppCompatDelegate
是一个笼统类,咱们能够很轻松的找到它的实现类AppCompatDelegateImpl
。
咱们能够在这里看到setContentView
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
能够看到,咱们供给的LayoutId,最终会添加到contentParent这个View上,那么mSubDecor是从哪儿来的?看到调用办法的姓名,ensure sub decor ,子装修视图,想必在这里。咱们持续往下看:
private void ensureSubDecor() {
// 检查子装修(sub decor)是否现已设置
if (!mSubDecorInstalled) {
// 创立子装修视图
mSubDecor = createSubDecor();
//**
}
}
看样子藏在createSubDecor
中, gogogo:
private ViewGroup createSubDecor() {
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
//**省掉 获取当前上下文的主题特点,设置对应的款式
a.recycle();
// 确保窗口已装置其装修
ensureWindow();
mWindow.getDecorView();
// 获取布局填充器
final LayoutInflater inflater = LayoutInflater.from(mContext);
ViewGroup subDecor = null;
// 依据是否有标题和是否为起浮窗口来决定运用哪个布局
if (!mWindowNoTitle) {
if (mIsFloating) {
// 假如是起浮窗口,则运用对话框标题装修
subDecor = (ViewGroup) inflater.inflate(
R.layout.abc_dialog_title_material, null);
//**
} else if (mHasActionBar) {
// 假如有动作栏,则运用特定主题创立布局
// 运用主题化上下文填充视图并设置为内容视图
subDecor = (ViewGroup) LayoutInflater.from(themedContext)
.inflate(R.layout.abc_screen_toolbar, null);
//**
}
} else {
// 依据是否覆盖动作形式挑选不同布局
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);
//**
}
}
//**
// 将窗口的内容视图设置为subDecor
mWindow.setContentView(subDecor);
//**
return subDecor;
}
能够看到这个办法回来一个装备好的 subDecor
,subDecor
运用的是体系的布局,依据装备的不同,运用了不同的xml
。回来给咱们用于添加LayoutId
,可是它是怎么显现的仍是不清楚,可是注意到mWindow.setContentView(subDecor);
。
咱们点下去一看,回来到Window类:
/**
* Convenience for
* {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}
* 将屏幕内容设置为显式视图。此视图直接放置在屏幕的视图层次结构中。它本身能够是一个杂乱的视图层次结构。
* @param view The desired content to display.
* @see #setContentView(View, android.view.ViewGroup.LayoutParams)
*/
public abstract void setContentView(View view);
明显这是整个视图显现过程中十分核心的一步。可是它是笼统的!不过依据咱们小学二年级就学过的
The only existing implementation of this abstract class is android.view.PhoneWindow, which you should instantiate when needing a Window.
是的,Window
只要一个实现类-PhoneWindow
。
三、PhoneWindow
咱们找到PhoneWindow
:
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
if (mContentParent == null) {
// 假如内容父视图还未创立,则进行装置
installDecor();
}
//**
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//**
} else {
// 将视图添加到内容父视图中
mContentParent.addView(view, params);
}
//**
}
咱们看看怎么初始化mContentParent
,走进installDecor()
的心里。
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
// 创立窗口装修视图
mDecor = generateDecor(-1);
//**
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
// 生成并设置内容布局
mContentParent = generateLayout(mDecor);
//**
} else {
//**
}
// 省掉涉及到过渡办理器和动画的装备
}
}
在这里咱们能够看到两个,generateDecor
,可是在createSubDecor
中咱们现已创立过了:
protected ViewGroup generateLayout(DecorView decor) {
// ... 省掉了一部分特点设置代码 ...
// 依据窗口特性挑选布局资源
int layoutResource;
int features = getLocalFeatures();
// 依据不同的特性标志挑选不同的布局资源
if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_title_icons;
}
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) !=
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
layoutResource = R.layout.screen_progress;
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
if (mIsFloating) {
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_custom_title;
}
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
if (mIsFloating) {
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;
}
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
layoutResource = R.layout.screen_simple;
}
// 装修视图开始改变
mDecor.startChanging();
// 运用LayoutInflater加载布局资源
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
// 获取内容父视图
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
// ... 省掉了其他设置代码 ...
// 装修视图完结改变
mDecor.finishChanging();
return contentParent;
}
在这个办法中,会依据不同的特性标志挑选不同的布局资源,可是这些布局都有一个显著的特点。他们都有一个id为content的FrameLayout。便是那个老生常谈的android.R.id.content
~。
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
四、倒着回去
至此,咱们知道了PhoneWindow
的setContentView
中的contentParent
来自哪里:
那咱们也是知道了AppCompatDelegate
中的setContentView
的contentParent
来自哪里:
捋一下:
- 在活动的
onCreate
办法中调用setContentView
,传入布局资源ID或者直接传入一个视图(View)目标。 - 从
AppCompatDelegate
中调用Window.setContentView
。 -
PhoneWindow
目标担任创立和办理顶层视图容器,DecorView
。假如DecorView
还未创立,Window
会经过调用generateDecor
办法来创立它。DecorView中一定有一个ID为android.R.id.content
的FrameLayout。 -
AppCompatDelegateImpl
将视图添加到android.R.id.content
。
五、Activity
为什么没有提及Activity呢?仔细的咱们应该发现了,AppCompatActivity的setContentView是一个重写办法,它完全重写了父类。
Overrides method in Activity
咱们往上看一下:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
明显,最终Activity也是回到了PhoneWindow,至于为什么要这样呢?你猜~~~~嘻嘻。
六、LayoutInflater
不知道咱们有没有注意到PhoneWindow和AppCompatDelegate中的LayoutInflater,那么DecorView和咱们的Layout是怎么渲染到屏幕的呢?请看LayoutInflater。
用Google话来说:
将布局 XML 文件实例化到其相应的 View 目标中
请见下回分解。
七、总结
啊,有点困。冰美式压不住我的睡意。刀了。下一篇咱们来说说LayoutInflater。
假如您有任何疑问、对文章写的不满意、发现过错或者有更好的办法,欢迎在评论、私信或邮件中提出,十分感谢您的支持。