一篇文章搞定《CoordinatorLayout完成电商首页》
- 本文前言
- 怎样到达ViewRootImpl
- 过程如下:
- 流程图小结:
- 到达ViewRootImpl做了什么
- 第一步:setView()
- 第二步:performTraversals()
- 第三步:DecorView中的Measure()、Layout()、Draw()
- View的最终绘制
- 首先要知道什么是View树
- onMeasure()
- onLayout()
- onDraw()
本文前言
像事件的分发一样,View的绘制流程我也分成了三部分来讲
分别为:1、怎样到达ViewRootImpl。2、到达ViewRootImpl做了什么。3、View的最终绘制
之后我会利用一个自定义View的实例来反刍这篇文章,像《一篇文章搞定搞定事件分发》一样。
怎样到达ViewRootImpl
过程如下:
1、首先View是在Activity的onCreate阶段通过setContentView解析xml加载进去的。也就是View是在OnCreate阶段加到缓存中的。
2、加在哪个缓存中了呢:这个View对象会被添加到ActivityThread的ActivityClientRecord对象中(后面可以看到取出来添加到DecorView中),并放入ActivityThread的mActivities集合中缓存当前Activity对象的状态信息。
3、ActivityThread.handleResumeActivity()是在Activity的生命周期中的onResume阶段调用的,用于将Activity设置为resumed状态。
4、ActivityThread.handleResumeActivity()会调用WindowManager的addView()方法来把Activity的Window视图添加到窗口上。源码如下:
@Override
public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
boolean isForward, String reason) {
.....
final Activity a = r.activity;
if (r.window == null && !a.mFinished && willBeVisible) {
.....
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
if (!a.mWindowAdded) {
......
a.mWindowAdded = true;
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
5、可以看到ActivityThread.handleResumeActivity()方法中,会先通过Activity的getWindow()方法获取到当前Activity的Window,设置相关的Window参数,接着调用WindowManager的addView方法将Window的视图加入到窗口中。
6、这里面ActivityClientRecord只是Activity的一个信息类,记录一下Activity的状态、所属进程等相关信息。
7、WindowManager实现类为WindowManagerImpl,WindowManagerImpl中addView()方法又会调用WindowManagerGlobal的addView()方法。源码如下:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
······
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
//即将开始流程绘制
root.setView(view, wparams, panelParentView);
·······
}
8、可以看到他会创建出我们熟知的ViewRootImpl。并将我们的View Set给ViewRootImpl进行管理。
上面的这个过程可以理解为Android ManagerService的功劳,通过管理Activity的生命周期从而加载我们的View。
流程图小结:
到达ViewRootImpl做了什么
像事件的分发一样,最终View都交给了ViewRootImpl来进行管理。这也是ViewRootImpl代码上万行的原因。经过不断的迭代,View的体积已经很庞大了。这也许是Jetpack Compose出现的原因吧,来替换掉这庞大的View体系,毕竟庞大就难以维护。
下面看看ViewRootImpl对View的绘制做了哪些关键的步骤。
第一步:setView()
首先上面讲到了ViewRootImpl的setView。那setView做了什么呢?
我们知道ViewRootImpl中的mView就是我们的根布局DecorView这个在事件的分发中也有提到。这里再说一遍吧:
其实上面认真读代码的人已经发现了,就在第一步handleResumeActivity方法中。我们先是创建了window之后获取了decor,并通过windowsManager的wm.addView(decor, l)设置给了下一步。最终到达ViewRootImpl。
下面是ViewRootImpl的setView方法。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
if (mView == null) {
mView = view;
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
}
}
可以看到ViewRootImpl首先将window给他的view和自己的mView进行了绑定。毕竟自己家的小孩才能管,对吧。
第二步:performTraversals()
下一步就来到了View绘制的关键方法performTraversals()。
上面可以看到执行了requestLayout()方法,requestLayout()方法中会异步执行performTraversals()方法,View的三大流程都是在该方法中执行的。
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
到这儿我们算是明白View的绘制流程是从哪儿开始的,接下来分析这个过程到底是怎么做的。
直接简化一下performTraversals的代码便于理解(简化把----嘻嘻)
private void performTraversals() {
//计算DecorView根View的MeasureSpecint childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
performLayout(lp, mWidth, mHeight);
performDraw();
}
真正的奥秘就在performMeasure()、performLayout()、performDraw()大家看着熟悉吗是不是想到了,说烂嘴的onMeasure()、onLayout()、onDraw()方法。
没错昂、看来你很有学习Android的脑瓜。他最终就调用了我们的mView也就是DecorView的三大绘制方法。
第三步:DecorView中的Measure()、Layout()、Draw()
让我们来看一下:
performMeasure()
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
performLayout()
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
final View host = mView;
if (host == null) {
return;
}
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
performDraw()
这个方法中没有直接调用mView的Draw。是先调用draw方法从而调用drawSoftware。最后还是一样会调用Draw。
源码如下:都被简化的了方便理解
private boolean performDraw() {
try {
boolean canUseAsync = draw(fullRedrawNeeded, usingAsyncReport && mSyncBuffer);
....
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
private boolean draw(boolean fullRedrawNeeded, boolean forceDraw) {
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
try {
....
mView.draw(canvas);
....
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
最后可以看到调用了mView的Measure、Layout、Draw方法。
我们点进去发现,在View的Measure、Layout、Draw会调用我们熟知的onMeasure()、onLayout()、onDraw()方法。这个源码就不粘贴出来了。大家可以点进去看一下。
View的最终绘制
所谓View的最终绘制就是。简单来说就是View是如果通过onMeasure()、onLayout()、onDraw(),最终绘制的界面上的。
下面会对这三个重要方法进行知识解析,随后我会以两篇自定义View的文章去用例子来反刍这个知识点。