一.分析层层调用的关键代码
-
设置布局文件:每个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 {
private DecorView mDecor;
ViewGroup mContentParent;
private LayoutInflater mLayoutInflater;
public PhoneWindow(Context context) {
super(context);
mLayoutInflater = LayoutInflater.from(context);
}
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
...
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
}
private void installDecor() {
this.mForceDecorInstall = false;
if (this.mDecor == null) {
this.mDecor = this.generateDecor(-1);
} else {
this.mDecor.setWindow(this);
}
if (this.mContentParent == null) {
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) {
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();
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 {
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
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) 绘制组件,自己实现。
参考文档:https://hanshuliang.blog.csdn.net/article/details/83019583
|