View 的绘制流程
目录
接着我们之前分析的 activity 启动流程,我们继续分析一下 activity启动后,View 的绘制流程
不清楚的可以看一下activity 启动流程:activity 启动流程
源码版本:android-27
上一篇的启动流程中,最后 走到了 ViewRootImpl 中的 setView() 方法,我们顺着这个继续看下去。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
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.
// 这里就是我们 View 绘制的入口了
requestLayout();
......
}
}
}
requestLayout() 就是 View 的入口,也是开始的地方,我们顺着 requestLayout() 看进去:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
// 进入 scheduleTraversals()
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 进入 Callback 回调
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
// postCallback 进入 mTraversalRunnable
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
// 继续进入看 TraversalRunnable()
final class TraversalRunnable implements Runnable {
@Override
public void run() {
// 我们发现走进了 doTraversal() 方法
doTraversal();
}
}
// 进入 doTraversal()
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
// 最后我们 进入了 View 绘制真正的入口,也是大家普遍分析的 performTraversals() 方法
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
我们继续看 performTraversals(),这个方法是整个绘制过程的主体,他控制着整个绘制流程。
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
if (DBG) {
System.out.println("======================================");
System.out.println("performTraversals");
host.debug();
}
......
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
......
// Ask host how big it wants to be
// View 的测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
......
// View 的布局
performLayout(lp, mWidth, mHeight);
......
// View 的绘制
performDraw();
}
这里先贴一段 View 绘制的示例代码,方便我们 可以根据自己写的 xml 布局 去看源码
<!-- 示例代码 -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
可以看到 LinearLayout 里面 嵌了一层 LinearLayout ,之后 写了两个 TextView
测量Measure
performMeasure:View ——> Measure() ——> onMeaure()
现在我们继续看源码,从 performTraversals() 方法中,我们可以看到 三大流程,测量、布局、绘制。所以我们现在先看 测量。
跟踪源码,进入到performMeasure方法
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
......
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
......
}
我们继续看 measure 方法,我们会发现调用了 onMeasure() 方法,也就是 我们重写的 测量方法:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
......
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
......
}
这个时候,根据我们的 实例代码,我们的根布局 是 LinearLayout ,所以我们去 LinearLayout 中看 OnMeasure() 方法:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 在这里,我们可以看到,onMeasure() 方法中,根据我们设置的 Orientation
// 进入不同的方法,我们就根据示例,进入了 measureVertical() 方法
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
进入 measureVertical() 方法,父 View 的 measure 的过程会先测量子 View,等子 View测量结果出来后,再来测量自己:
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
......
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
......
// See how tall everyone is. Also remember max width.
// for 循环,遍历所有的子 View
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child == null) {
mTotalLength += measureNullChild(i);
continue;
}
......
// Determine how big this child would like to be. If this or
// previous children have given a weight, then we allow it to
// use all available space (and we will shrink things later
// if needed).
final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
// 测量子 View
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
......
}
接着 我们进入 measureChildBeforeLayout()方法中:
void measureChildBeforeLayout(View child, int childIndex,
int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
int totalHeight) {
measureChildWithMargins(child, widthMeasureSpec, totalWidth,
heightMeasureSpec, totalHeight);
}
这里只有一个方法,就是 measureChildWidthMargins(),measureChildWithMargins就是用来测量某个子View的:
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
// 子View的 LayoutParams
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
// 请注意 getChildMeasureSpec() 这个方法,这里就告诉我们具体的测量模式、大小是怎么
// 得到的。它根据父 View 的测量规格和父 View 自己的 Padding,
// 还有子 View 的 Margin 和已经用掉的空间大小(widthUsed),就能算出子View的MeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
// 子View 用获取到的 MeasureSpec 去测量自己,
// 如果子View是 ViewGroup 那还会递归往下测量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
下面,我们进入 getChildMeasureSpec() 来看一下是怎样测量的:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
// spec:指的是 父 View 的 MeasureSpec 测量规格
// childDimension:表示子 View 内部的 Layoutparams 属性的值
int specMode = MeasureSpec.getMode(spec); //获得父View的mode
int specSize = MeasureSpec.getSize(spec); //获得父View的大小
//获取子View的大小
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
// 父 View 测量模式是 EXACTLY:
case MeasureSpec.EXACTLY:
// 子View的 width 或 height 是个精确值 比如 100dp
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
}
// 子 View 的 width 或 height 为 MATCH_PARENT
else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
}
// 子 View 的 width 或 height 为 WRAP_CONTENT
else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
// 父 View 测量模式是 AT_MOST:
case MeasureSpec.AT_MOST:
// 子 View的 width 或 height 是个精确值 比如 100dp
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension; //childDimension 为子 View 的大小
resultMode = MeasureSpec.EXACTLY;
}
// 子 View 的 width 或 height 为 MATCH_PARENT
else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
// 子 View 的 width 或 height 为 WRAP_CONTENT
else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
// 父 View 测量模式是 AT_MOST(这个很少用到):
case MeasureSpec.UNSPECIFIED:
// 子 View的 width 或 height 是个精确值 比如 100dp
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
}
// 子 View 的 width 或 height 为 MATCH_PARENT
else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
// 子 View 的 width 或 height 为 WRAP_CONTENT
else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = 0; //size大小为0
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//根据计算获取 mode 和 size 去创建 measureSpec 对象
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
可以看出 模式 和 大小 是由 父布局和 自己决定的,通过 getChildMeasureSpec 方法判断的:
1、如果父 View 的 MeasureSpec 是 EXACTLY,说明父View的大小是 match_parent 或者是固定值(比如 100dp)
- 如果如果子 View 是确定的值(100dp),那么 MeasureSpec 的 mode = EXACTLY,大小 size = 你在子 View填的那个值(100dp)
- 如果子View 是MATCH_PARENT,那么子 View 的 size = 父 View 的 size,mode = EXACTLY
- 如果子View 是WRAP_CONTENT,那么子View MeasureSpec mode 应该是 AT_MOST,而 size 为父View的 size,表示的意思就是子 View 的大小没有确切的值,子 View 的大小最大为父 View的大小,不能超过父View的大小,也就是 AT_MOST 模式
2、如果父 View 的 MeasureSpec 是AT_MOST,说明父View的大小是不确定,为 wrap_content
- 如果如果子 View 是确定的值(100dp),那么 MeasureSpec 的 mode = EXACTLY,大小 size = 你在子 View填的那个值(100dp)
- 如果子View 是 MATCH_PARENT,由于父容器自己的大小不确定,导致子 View 的大小也不确定,就只知道最大就是父View的大小,所以子 View 的大小 size = 父 View 的 size,mode = AT_MOST
- 如果子View 是WRAP_CONTENT,那么子 View MeasureSpec mode 的就是AT_MOST,而size 暂时就是父View的 size。
经过一次次的测量子 View,最后会返回到 measureVertical() 中,等到所有子 View 测量完毕后,经过一系列的计算,通过 setMeasureDimension() 方法设置自己的宽高
对于我们的示例 LinearLayout 来说,就会 childHeight = child.getMeasuredHeight() + share; 不断的累计高度。
对于 RelativeLayout 来说,就会 height = Math.max(height, mLayoutParams.height); 不断比较找子 View 中最高的。
总的来说:onMeasure() 就是 父 View 不断的 for 循环测量子 View,等到子 View 测量完毕后,再去测量自己的宽高,最后通过 setMeasureDimension() 进行测量。所以我们重写 onMeasure 最后都需要调用 setMeasureDimension() 方法。
布局Layout
performLayout:View ——> layout() ——> onLayout()
layout 的作就是 ViewGroup 用来确定子 View 的位置。在 ViewGroup 确定位置后,会在 onLayout 中遍历所有的子 View,并调用child.layout 方法,在 layout 方法中又会调用 onLayout 方法。
绘制Draw
performDraw:View ——> draw() ——> onDraw()
drawBackground():画背景
onDraw(canvas):画自己
dispatchDraw(canvas):画子 View 不断循环调用子 View 的draw()
draw 中,会绘制背景,对 View 的内容进行绘制 以及其他的操作,但是注意一个,就是 ViewGroup 默认不执行 onDraw 方法
我们自定义ViewGroup 时,要执行onDraw 方法,有几种方式:
1、将onDraw 替换为 dispatchDraw 2、构造函数中设置背景透明 3、构造函数中调用 setWillNotDraw(false)
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
总结
View 绘制流程简述
第一步 performMeasure():用于指定和测量所有空间的宽高,对于 ViewGroup,先去测量里面的子 View,根据子 View 的宽高再来计算和指定自己的宽高,对于 View,它的宽高由自己和父布局实现的。
第二步 performlayout():用于摆放子布局,for 循环所有的子 View,用 child.layout(),摆放 childView
第三步 performDraw():用于绘制自己还有子 View,对于 ViewGroup,首先绘制自己的背景,然后 for 循环绘制子 View 调用子 View 的 draw() 方法。对于 view,绘制自己的背景,绘制自己显示的内容
思考
1、如果要获取 View 的宽高,前提肯定是需要调用测量方法,测量完毕之后才能获取宽高属性
比如:activity 启动时,获取 view 的宽高,我们可以用下面的方式:
①、Activity 重写 onWindowFocusChanged:调用这个方法时,view 已经初始化完毕
②、调用 view.post(runnable):post只是将 runnable 添加到消息队列中,等到 Looper 的时候才调用,这是 view已经初始化好了
2、View 的绘制流程,是在 onResume 之后才开始
3、addView、setText、setVisibility 等等 会调用 requestLayout() invalidate(ture),重新走一遍 View 绘制流程
4、优化的时候,可以根据源码写代码的时候进行优化,比如 使用 invalidate(),我们可以去减少 ondraw() 调用次数
5、不要布局嵌套 等等