View绘制流程

流程

每个Activity都有一个PhoneWindow对象,每个PhoneWindow对应一个DoctorView 和一个ViewRootImpl,Window和View 通过ViewRootImpl建立联系,一个Activity的绘制是从ViewRootImpl的performTraversals来发起的三个流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private void performTraversals() { 
......
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
......
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
......
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
......
mView.draw(canvas);
......
}

private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.EXACTLY);
break;
......
}
return measureSpec;
}

measuerSpace

三种模式

  • UPSPECIFIED 父容器未指定大小,因此这个模式下子测量的结果为0
  • EXACTLY父容器已经为子容器设置了尺寸很明确不用改变
  • AT_MOST父容器规定了最大多大,所以自容器不能超过这个大小

onMeasure

1
2
3
4
5
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(
getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

关键方法setMeasuredDimension() 这个方法主要是设置View的高度,这个View测量结束了这个View的宽高也就测量结束了,必须在onMeasure中调用

1
2
3
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

首先先算出背景和宽度那个大,然后给出一大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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来计算这个View的大小,

每个View 的派生类TextView等的OnMeasure方法都不一样,因此不能代表其他View

ViewGroup的Measure过程

  • ViewGroup没有实现onMeasure 那么其他类的onMeasure是怎么做的呢?,看FrameLayout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();

final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();

int maxHeight = 0;
int maxWidth = 0;
int childState = 0;

for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
}
}
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));

}

其实就是遍历所有的子View然后执行子View的onMeasure方法来测量子View的大小

onLayout

onMeasure只是用来测量大小的,onLayout才是计算View具体在哪个位置的 ViewGroup就是算出来了margin和padding的值,然后再执行子View的的位置。 margin值在layoutPara当中

1
2
3
4
5
6
7
8
9
10
11
12
public void layout(int l, int t, int r, int b) {
if (!this.mSuppressLayout && (this.mTransition == null || !this.mTransition.isChangingLayout())) {
if (this.mTransition != null) {
this.mTransition.layoutChange(this);
}

super.layout(l, t, r, b);
} else {
this.mLayoutCalledWhileSuppressed = true;
}

}
  • View和ViewGroup的onLayout都是空实现
  • Layout才是真正确定屏幕上位置的那一个人 其实onLayout就是确定子View在父View的位置,那么子View的位置呢? 例如Framelayout,就是通过layoutChildren()来遍历子View计算的

onDraw

draw分为6个步骤

  • 第一步绘制背景 主要是将背景绘制到canvas上面
  • 第三步 对View的内容进行绘制View和ViewGroup都没有实现,具体View具体实现
  • 第四步 对所有子View进行绘制dispatchDraw(canvas) View是一个空方法,ViewGroup的dispatchDraw(canvas) 实现是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 if (clipToPadding) {
saveCount = canvas.save();
canvas.clipRect(this.mScrollX + this.mPaddingLeft, this.mScrollY + this.mPaddingTop, this.mScrollX + this.mRight - this.mLeft - this.mPaddingRight, this.mScrollY + this.mBottom - this.mTop - this.mPaddingBottom);
}
·······
if ((flags & 1024) == 0) {
for(i = 0; i < count; ++i) {
child = children[i];
if ((child.mViewFlags & 12) == 0 || child.getAnimation() != null) {
more |= this.drawChild(canvas, child, drawingTime);
}
}
} else {
for(i = 0; i < count; ++i) {
child = children[this.getChildDrawingOrder(count, i)];
if ((child.mViewFlags & 12) == 0 || child.getAnimation() != null) {
more |= this.drawChild(canvas, child, drawingTime);
}
}
}

其实就是调用draChild方法,这个方法是干嘛的?就是执行了子View的draw方法

其实就是给子View分配Canvas剪裁区域,一般不用实现这个方法,ViewGroup都给写好了,设置给剪裁区域给子View绘制就行了

  • 第六步 不是重点 onDrawScrollBars的注释:请求绘制横向和纵向的scrollbar,scorllbar只在他们唤醒的时候绘制

img

谢谢您的鼓励~