您的位置:首页 > 移动开发 > Android开发

【Android】View绘制过程分析之measure

2014-02-22 00:17 375 查看
读源码,分析View的绘制过程,对自定义View的开发与理解有莫大的帮助。这是写本组文章的目的所在!

绘制View的过程分为3个阶段:

第1阶段,measure() 计算View应占空间大小
第2阶段,layout() 分配大小和位置到View及它的子View
第3阶段,draw() 绘制

首先,分析第1阶段,分析过程的注释标记在以下代码中。

/**
* 通过这个方法来计算View应该占多大的空间,
* 父View通过widthMeasureSpec和heightMeasureSpec这两个参数来约束了此View的空间大小
* 实际的计算工作在onMeasure(int,int)方法中实现,子类应该覆写onMeasure(int,int)方法方法提供确切的大小
*/
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
// 子类只需要覆写onMeasure(int,int)方法,来做计算View空间的相关工作
onMeasure(widthMeasureSpec, heightMeasureSpec);
//子类必须(在onMeasure(int,int)方法中)调用setMeasuredDimension()方法,否则抛IllegalStateException异常
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
}

/**
* 计算此View和它的内容应占的空间大小。
* 注意1:在此方法中一定要调用setMeasuredDimension(int,int)来设置此View应占的大小
* 注意2:若是此View是布局,则要遍历所有子View,调子View的measure(int, int)方法为子View计算大小
* View类onMeasure(int, int)方法的默认实现如下:
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
//getMeasuredWidth()和getMeasuredHeight()方法获取到的就是在这里设置的值
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
//或运算,标记已经调用过setMeasuredDimension(int,int)方法,在measure(int,int)方法中通过与运算来判断
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

/**
* 用于获取默认大小的工具方法
* 若父View有约束此View大小(通过measureSpec),则遵循父View分配的大小;
* 否则以传进来的参数size作为大小。size怎么得来?且继续看下去。
*/
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;
}

/**
* 获取建议View应该使用的最小宽度。取值为“mMinWidth”和“背景图片最小宽度”两者中的最大值。
* 其中mMinWidth从哪里来?使用时,一是通过调用setMinimumWidth(int)方法设置;二是通过布局文件中属性android:minWidth设置
*
*/
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

/**
* Drawable类的方法,若Drawable是图片资源,有固定的宽度,则返回固定的宽度;否返回0
*/
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}

/**
* getSuggestedMinimumHeight(int,int)方法同理,略。
*/

/**
* 若是此View是布局,以FrameLayout为例:
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//省略部分代码
//设置自己的大小
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));

//遍历子View,计算子View的大小
count = mMatchParentChildren.size();
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//以下操作,创建MeasureSpec传递给子View, 即FrameLayout给子View分配大小
int childWidthMeasureSpec;
int childHeightMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) {
//子View的宽度 = 父View的宽度 - 父View的Padding - 子View的Margin
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() -
getPaddingLeftWithForeground() - getPaddingRightWithForeground() -
lp.leftMargin - lp.rightMargin,
MeasureSpec.EXACTLY);
} else {
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
}
if (lp.height == LayoutParams.MATCH_PARENT) {
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() -
getPaddingTopWithForeground() - getPaddingBottomWithForeground() -
lp.topMargin - lp.bottomMargin,
MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
lp.topMargin + lp.bottomMargin,
lp.height);
}
//调用子View的measure(int, int)方法。
//根据上面的计算得知:子View的measure()方法中得到的childWidthMeasureSpec不包括子View的Margin值
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}

/**
* 来看一下MeasureSpec类的工具方法:
* 参数size为实际大小值,
* 参数mode为模式,可取值:
* EXACTLY(父View确切地给定了子View的大小,不顾View可能超出这个大小)
* AT_MOST(尽可能大,  The child can be as large as it wants up to the specified size.)
* UNSPECIFIED(父View没有约束子View的大小,子View想要多大就多大)
*/
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;			//原来是实际大小值与模式值之和
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}


@容新华技术博客 - http://blog.csdn.net/rongxinhua - 原创文章,转载请注明出处
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息