View的绘制流程主要分为散步,measure,layout以及draw,接下来,我们就从源码角度分析这个步骤。
测量(measure)
测量是绘制的第一步,用来决定View或者ViewGroup的测量宽高,这里讲View和ViewGroup分别讨论下
1.View的测量过程
概述:View的测量过程相对简单,直接可以通过onMeasure方法完成测量工作。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
首先通过getSuggestedMinimumHeight获取View的最小高度(宽度),如果没设置背景则返回minHeight,否则返回背景的原始高度和minHeight的较大值
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ?
mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
其次,计算出View的测量宽度
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;
}
2.ViewGroup的测量过程
概述:ViewGroup的测量工作分为两步,完成自己的测量工作,遍历子View并完成他们的测量工作,因此对应的方法有两个onMeasure
和measureChildren
measureChildren
遍历测量每一个子View
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
//View的状态不能是GONE
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
每一个子View调用自己的measure
方法
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec =
getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec =
getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
onMeasure
测量自己的过程需要根据不同的ViewGroup自己实现,相对简单的ViewGroup有FrameLayout和LinearLayout,但是FrameLayout的重叠属性导致分析起来不太清晰,这里拿LinearLayout举个例子。
区分LinearLayout的布局类型
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) { //垂直布局
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else { //水平布局
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
拿垂直方向距离,计算高度和宽度
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
final int count = getVirtualChildCount();
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
//View为null则直接跳过
if (child == null) {
mTotalLength += measureNullChild(i);
continue;
}
//View为GONE也直接跳过
if (child.getVisibility() == View.GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
//将divider的高度算到最终高度里
if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerHeight;
}
//如果通过权重来分配空间,则根据权重计算mTotalLength
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
totalWeight += lp.weight;
if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
...
} else {
measureChildBeforeLayout(child, i, widthMeasureSpec, 0, heightMeasureSpec, usedHeight);
final int childHeight = child.getMeasuredHeight();
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight +
lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
}
// Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;
// Check against our minimum height
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
//计算LinearLayout的宽度
...
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
}
measure过程完成后,我们就可以获取到View的测量宽高,但是,这还不是View的最终宽高,最终宽高是在下面layout
中完成的。
布局(layout)
布局用来确定View或者ViewGroup的位置,如果是ViewGroup的话,与measure
一样会遍历所有的子元素并调用layout方法。
1.View的layout过程
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
//获取四个顶点的位置
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//调用View的onLayout方法将View放置到指定位置。
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
//执行onLayoutChangeListener
if (li != null && li.mOnLayoutChangeListeners != null) {
...
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
}
获取四个顶点的位置
protected boolean setFrame(int left, int top, int right, int bottom) {
...
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
...
//计算新的宽高
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
//View的大小是否变化
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
//刷新
invalidate(sizeChanged);
//设置新的四个顶点的位置
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
...
}
2.ViewGroup的layout过程
这里依然以LinearLayout为例,首先区分线性布局类型
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
计算自身的位置以及遍历子View并计算他们的位置
void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;
int childTop;
int childLeft;
// 计算View右侧的结束位置
final int width = right - left;
int childRight = width - mPaddingRight;
// 计算留给子View的剩余空间
int childSpace = width - paddingLeft - mPaddingRight;
//计算子View个数
final int count = getVirtualChildCount();
//根据Gravity计算childTop
...
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
//计算子View的测量宽高
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
//计算childLeft
...
//如果有divider,那么childTop要加上它。
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
childTop += lp.topMargin;
//调用子View的layout方法。
setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
}
}
绘制(draw)
绘制过程比较简单,主要6步
public void draw(Canvas canvas) {
...
//画背景
if (!dirtyOpaque) {
drawBackground(canvas);
}
//绘制自己的内容
if (!dirtyOpaque) onDraw(canvas);
//绘制子View(如果有的话。)
dispatchDraw(canvas);
...
//绘制前景和滚动条
onDrawForeground(canvas);
}
获取View宽高的方法
1.onWindowFocusChanged
该方法的注释:该方法在得到或者失去焦点时调用,这是确定activity是否对用户可见的指示器。不过该方法随着focus的得到和失去会多次调用。
2.ViewTreeObserver
可以通过ViewTreeObserver的接口回调完成View宽高的获取
3.View.post
通过post将一个runnable扔到消息队列的末尾,然后调用
测量宽高和真实宽高的区别
测量宽高和真实宽高基本一样,只不过测量宽高是在measure
时期确定的,而真实宽高是在layout
时期决定的,两者的确定时期不同。日常开发过程中,我们可以认为是测量宽高就是真实宽高。但不绝对,我们可以在View的Layout方法中手动修改left,top,right,bottom的值导致他们不一样。
网友评论