一.分析层层调用的关键代码
- 设置布局文件:每个Activity的onCreate()中 setContentView(R.layout.xx)。
- 实际是调用Window抽象类的方法:this.getWindow().setContentView(R.layout.xx)。
- Window唯一实现类PhoneWindow实现的setContentView():
关键调用方法:
1)installDecor()
1> 创建布局容器 : 先创建顶层View DecorView, 通过 mDecor 创建 ViewGroup布局容器。
2> 加载基础布局 : 调用 generateLayout(), 处理 requestFeature 设置, 根据处理结果加载不同的基础布局;
默认加载布局R.layout.screen_simple,垂直方向线性布局,包含状态栏ViewStub+Activity传入的自定义布局存放在FrameLayout。
2)mLayoutInflater.inflate() 加载布局文件
加载开发者自己的布局文件,LayoutInfater是PhoneWindow创建时初始化的布局加载器。
布局加载器加载过程:进行xml解析,解析出具体的组件和层级。
public class PhoneWindow extends Window implements Callback {
//窗口最顶层的 View
private DecorView mDecor;
//窗口的内容放置器
ViewGroup mContentParent;
//PhoneWindow 创建时初始化 mLayoutInflater 布局加载器成员变量
private LayoutInflater mLayoutInflater;
public PhoneWindow(Context context) {
super(context);
mLayoutInflater = LayoutInflater.from(context);
}
public void setContentView(int layoutResID) {
//对feature 属性进行初始化生效操作
if (mContentParent == null) {
//1.关键方法:installDecor()
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
...
} else {
//2.加载自己的布局文件
mLayoutInflater.inflate(layoutResID, mContentParent);
}
}
private void installDecor() {
this.mForceDecorInstall = false;
if (this.mDecor == null) {
//1.创建窗口最顶层的View:DecorView 成员变量对象
this.mDecor = this.generateDecor(-1);
} else {
this.mDecor.setWindow(this);
}
if (this.mContentParent == null) {
//2.创建内容存放容器对象
this.mContentParent = this.generateLayout(this.mDecor);
}
protected DecorView generateDecor(int featureId) {
return new DecorView((Context)context, featureId, this, this.getAttributes());
}
protected ViewGroup generateLayout(DecorView decor) {
//处理 requestFeature 设置
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
requestFeature(FEATURE_ACTION_BAR);
}
//这一组代码负责处理布局文件相关
int layoutResource;
int features = getLocalFeatures();
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {...
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {...
} else {
//默认状态下不做任何设置的布局
layoutResource = R.layout.screen_simple;
}
}
}
public abstract class LayoutInflater {
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
//进行 xml 解析, 解析出具体的组件及层级
final XmlResourceParser parser = res.getLayout(resource);
}
}
二.UI绘制流程
- 主线程ActivityThread会调用handleResumeActivity(),核心语句是wm.addView(decor, l); 将开发者开发的布局加载到 decor 基础布局中
- WIndowManager 的 addView 逻辑是定义在 WindowManagerGlobal 中,将View 对象, DecorView 对象、关联的ViewRootImpl 对象、布局参数对象三个队列全部交给 ViewRootImpl对象,root.setView(view, wparams, panelParentView);
- 在 setView 方法中调用了 requestLayout() --> scheduleTraversals() --> 回调调用了 TraversalRunnable 中的 run() --> doTraversal()–>ViewRootImpl 的 performTraversals() UI绘制的核心方法
三.UI绘制核心-View测量、布局、绘制流程
① 测量 Measure
② 布局 Layout
③ 绘制 Draw
1.绘制流程:
- 确定控件的起点位置:在ViewRootImpl.performTraversals() 中绘制一个矩形Rect来确定布局整体的位置。
- 布局测量:在performMeasure()中调用view.measure(),在其中计算组件大小,根据组件模式和设定的宽高。
- 布局摆放:在performLayout()中调用view.layout(),
- 布局绘制:在performDraw()中调用view.draw()
2.View布局测量:
- 进行布局测量 ViewRootImpl.performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
- getRootMeasureSpec():生成View测量的MeasureSpec布局参数。
MeasureSpec 规格 : 将 组件的大小 结合 布局参数中设置的转换规则
32位int值=2模式位+30位大小值
1> 与父容器相大小相等 MeasureSpec.EXACTLY : match_parent 。与父容器宽高完全一致, 充满父容器;
2> 与子控件相关 MeasureSpec.AT_MOST : wrap_content 子容器测量后的占用的大小, 最大不能超过父容器;
3> 设置固定值 MeasureSpec.EXACTLY : 直接设置一个 dp 或 px 值;
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
//基于窗口的根组件的布局参数
//计算出对应的 MeasureSpec 参数, windowSize 窗口的宽或高, rootDimension 窗口的某个方向的布局参数
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
//MATCH_PARENT 使用根 view 大小
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
//WRAP_CONTENT 设置根 view 的 最大大小;
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
//其他:固定的大小, 将根 view 设置成 指定的大小;
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
}
- View 的 onMeasure 方法:调用onMeasure(w,h);自己实现测量逻辑,自定义控件根据自己的业务需求,重写核心方法。
3.View布局摆放:
- 布局摆放Layout与布局测量Measure一样,都是顶层 View 循环调用子组件的 onMeasure/onLayout 方法, 直至调用到 最底层的 组件;
- 进行布局绘制 ViewRootImpl.performLayout()核心调用view的layout(),setFrame 设置一个布局摆放范围, 然后调用 onLayout()。
setFrame方法: 对组件的上下左右四个坐标进行初始化。将每次布局摆放的坐标记录下来提升性能,传入新的坐标时与旧坐标进行对比。不一致重新布局计算。 - View 的 onLayout方法:调用onLayout(w,h);自己实现摆放逻辑,计算出 组件的 左 上 右 下 的值。
4.View布局绘制:
- 进行布局绘制 ViewRootImpl.performDraw(),核心调用draw(),获取画布和要绘制的rect范围,传入drawSoftware()中。
- drawSoftware():
1> 先设置画布Canvas:mSurface.lockCanvas(dirty)
2> 正式绘制:调用mView.draw(canvas)
绘制流程:
① 绘制背景,
② 图层保存, (如果有必要, 保存当前画布层, 以便进行渐变绘制)
③ 绘制组件内容,
④ 绘制子组件,
⑤ 图层恢复, (如果有必要, 恢复画布层, 绘制渐变内容)
⑥ 绘制装饰内容; - View 的 onDraw(canvas) 绘制组件,自己实现。