UI绘制流程及原理
UI绘制流程及原理
setContentView解析
首先,我们的setContentView是在我们的Activity.java类中发现为getWindow().setContentView(layoutResID),其中的getWindow来自Window类,而PhoneWindow为Window类的唯一实现类,所以可以在PhoneWindow中找到setContentView的方法。在这个方法中,可以看到调用了installDecor()方法和mLayoutInflater.inflate(layoutResID, mContentParent),其中后一个为将layoutResID传入到mContentParent容器中。这样我们可以看下installDecor()到底做了什么:
installDecor()方法
进入installDecor()方法我们可以看到此方法做了两件事情:
1.通过generateDecor()方法,返回新创建的DecorView布局;
2.将得到的DecorView传到generateLayout(mDecor)方法中,得到mContentParent容器;
在这个方法中,我们可以找到这两行代码:
int layoutResource;
int features = getLocalFeatures();
其中layoutResource为系统布局id,根据features的不同,赋给layoutResource不同的布局。一直到
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource)
将layoutResource传入到DecorView的onResourcesLoaded()方法中,通过调用addView方法将layoutResource添加到DecorView上。
接着通过ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT)
,获取id为public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
的系统组件得到contentParent容器,并返回赋给mContentParent。
然后就可以在setContentView()方法中得到mContentParent容器,通过mLayoutInflater.inflate(layoutResID, mContentParent)
将我们传入的布局添加到mContentParent上。
View的绘制流程
绘制方法步骤
绘制入口:ActivityThread.handleResumeActivity()方法中:
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
// Normally the ViewRoot sets up callbacks with the Activity
// in addView->ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
} else {
// The activity will get a callback for this {@link LayoutParams} change
// earlier. However, at that time the decor will not be set (this is set
// in this method), so no action will be taken. This call ensures the
// callback occurs with the decor set.
a.onWindowAttributesChanged(l);
}
}
wm.addView(decor, l);将decorView添加到vm,即ViewManager中,ViewManager为接口类,通过a.getWindowManager()找到Window的getWindowManager,其中返回的mWindowManager的方法为mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
进入createLocalWindowManager(),为WindowManagerImpl.java类的方法,即vm.addView方法为WindowManagerImpl.java中的。在addView方法中调用了WindowManagerGlobal的addView方法,方法root.setView(view, wparams, panelParentView);
中又调用了requestLayout();
–>scheduleTraversals();
通过mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
调用了runable的run()方法–>doTraversal();
–>performTraversals();
中,得到:
1.测量方法:performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
2.布局方法:performLayout(lp, mWidth, mHeight);
3.绘制方法:performDraw();
详细绘制
performMeasure
调用performMeasure方法后,调用View.java的mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
方法,通过MeasureSpec方法计算布局的宽高,最后调用setMeasuredDimensionRaw()方法得到结果。
- 对于ViewGroup : measure() —> onMeasure()(需要测量子View的宽高) -> measureChildWithMargins() -> 子View调用measure()测量宽高(传入的MeasureSpec为父布局的值) -> setMeasuredDimension() -> setMeasuredDimensionRaw()(保存自己的宽高)
- 对于View : measure() —> onMeasure() -> setMeasuredDimension() -> setMeasuredDimensionRaw()(保存自己的宽高)
MeasureSpec测量
其中有三种模式:
- UNSPECIFIED:父容器不对View做任何限制,一般系统内部使用
- EXACTLY : 父容器检测出View的大小,View的大小就是SpecSize
对应xml的LayoutParams.MATCH_PARENT 或者 固定尺寸大小 - AT_MOST : 父容器指定一个可用大小,View的大小不能超过这个值
对应xml的LayoutParams.WARP_CONTENT
其中makeMeasureSpec()方法可以将mode和size进行组合成为MeasureSpec值。
getMode和getSize方法可以将MeasureSpec转为mode和size值。
在自定义View中,我们设置MATCH_PARENT和WRAP_CONTENT效果一样的原因为:
View的onMeasure方法setMeasuredDimension()
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
中的值调用了getDefaultSize()方法:
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
其中MeasureSpec.AT_MOST和MeasureSpec.EXACTLY所返回的size均为specSize
performLayout
- ViewGroup : layout(来确定自己的位置,4个点的位置) —> onLayout(进行子View的布局)
- View : layout(来确定自己的位置,4个点的位置)
自定义ViewGroup时要重写onLayout方法确定其中的子View的布局位置
performDraw
ViewGroup :
- 绘制背景 drawBackground(canvas)
- 绘制自己onDraw(canvas)
- 绘制子View dispatchDraw(canvas) ViewGroup已经实现了
- 绘制前景,滚动条等装饰onDrawForeground(canvas)
View :
- 绘制背景 drawBackground(canvas)
- 绘制自己onDraw(canvas)
- 绘制前景,滚动条等装饰onDrawForeground(canvas)