View的绘制流程
今天来讲讲View的绘制流程吧,这一块也是我比较薄弱的地方,而且之前没有什么耐心去看这一块。今天就来学习这一块的知识点。主要分为3部分:
-
View树的创建
-
ViewRootImpl
的创建 -
真正的绘制流程开始performTraversals
View树的创建
首先思考一个问题View的绘制从那里开始?关于这个问题我一开始也谷歌百度了很多东西,但是还是不能解决我的疑惑。所以今天我打算从根本上解决这个问题——从Activity的setContentView开始看。下面开始我的源码阅读过程。
Activity#setContentView
public void setContentView(@LayoutRes int layoutResID) {
//注释1 getWindow()返回的是一个PhoneWindow对象,所以下一步,就是要去PhoneWindow中去看看这里面做了什么。PhoneWindow也是Window类的唯一实现类
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
PhoneWindow#setContentView
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
//注释2 mContentParent一开始的时候为空,所以会执行installDecor函数,mContextParent是一个ViewGroup对象
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;
}
//installDecor的注释很长,我们一步一步看
private void installDecor() {
mForceDecorInstall = false;
//注释3 mDecor是一个DecorView,这是窗口的顶层视图,包含窗口装饰。首次进来的时候mDecor是空的,所以获取执行generateDecor,在generateDecor里面会去创建一个DecorView,注意这个函数是在PhoneWindow里面的,也就是说PhoneWindow里面的mDecor有了初始值,那么就是DecorView与PhoneWindow建立了关联。
if (mDecor == null) {
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
//注释4 注意这里mContentParent还是没有进行任何的初始化,所以还是为null,所以会执行generateLayout方法,而generateLayout的方法参数是上面干初始化的mDecor,而这个函数的返回值是给mContentParent赋值。
if (mContentParent == null) {
//这里面得到的是一个FrameLayout
mContentParent = generateLayout(mDecor);
//....省略未分析的代码
}
}
PhoneWindow#generateLayout
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
//...省略很多代码,主要是获取Activity主题的一些属性,然后根据属性配置
//注释5 这个默认情况下会为R.layout.screen_simple,screen_simple.xml的布局内容是一个LinearLayout里面包含两个子view,其中一个子view的id是content,并且是FrameLayout。
int layoutResource;
int features = getLocalFeatures();//获取Activity.java 代码的里面的配置,注意这里很上面的不一样,上面是根据Activity主题的配置,这里获取的是java代码里面的动态配置
//...省略很多代码,
mDecor.startChanging();
//注释6 参数为一个LayoutInflate,和刚刚的布局文件id,这里面会调用mLayoutInflater的inflater创建view,然后调用DecorView的addView方法,也就是说刚才那个布局里面的控件都加入了DecorView中,也是就DecorView包含了他们
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//注释7 调用findViewById,其实调用的是DecorView的findViewById方法,然后返回一个ViewGroup。而这个id正好是注释5里面提到的content,所以这里面相当于返回了一个FrameLayout
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
//省略一些配置代码
//注释8 这里面将FrameLayout返回
return contentParent;
}
好分析到这里,很多人已经蒙了,什么跟什么啊,一点绘制的流程都没讲。确实这里面还是没有讲解到view真正去绘制的代码,但是这里面的知识点确实很重要。如果直接讲解view的绘制流程的代码,很多人会不知道为什么执行到了具体哪一个步骤。所以上面的流程步骤先耐心看完吧。我们现在有了什么?总结一下,我们有了DecorView
,有了一个FrameLayout
,还有一些可能配置的信息,比如TitleBar,他们的关系是怎么样的呢?如下图。
想一个问题,这里面是系统的层面的布局给改好了,我们传入的布局呢?这时候就需要回到PhoneWindow
#setContentView里面了。
public void setContentView(int layoutResID) {
//省略已经分析过的代码
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)