Android View的绘制之measure过程初解。
前言
每个Android开发者,开发到一定程度后,都不可避免的涉及到各种自定义控件,各种性能优化的问题。
而学习和了解View的绘制过程,会对你的控件开发,性能优化等东西有很大的帮助。今天写这边博客,就是希望带大家从View和ViewGroup的基本的源码中,了解View的绘制过程。整个View的绘制涉及测量(measure),布局(layout),绘制(draw)等一系列过程。layout和draw相对比较简单,我后面的文章会继续跟进,今天这里我们来交流一下测量(measure)的过程。下面附上view绘制的流程图:
measure的目的
首先你应该要知道measure是用来做神马的。简单的来讲measure是为了对view的mMeasuredWidth和mMeasuredHeight这两个变量进行赋值,因此只要这两个变量赋值完成了,view的measure就完成了。目的就是那么简单。
measure的流程
要知道流程,我们必须知道源码中涉及的关键方法。对应的view以及viewGroup中关键方法如下。按调用顺序排列,为了篇幅清洁暂时没写入参。
- View中:
- public final void measue();
- protected void onMeasure()
- protected final void setMeasuredDimension()
- ViewGroup中:
- protected void measureChildren()
- protected void measureChild()
- protected void measureChildWithMargins()
- public static int getChildMeasureSpec()
我们来依次的研究这些方法。先从关键的view开始。因为赋值是是在view中进行。viewGroup中其实是遍历ViewTree中的所有子view,再调用view中的measure方法实现赋值。
预备知识
在讲解之前给大家复习一下MeasureSpec(测量细则).
请各位还不是特别了解的看官移步:MeasureSpec介绍
View
进入正题,知道这些,让我们一起开始读源码。
第一个方法measure():
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
//若widthMeasureSpec或heightMeasureSpec就调用onMeasure去赋值。
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(widthMeasureSpec, heightMeasureSpec);
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;
}
Tips:mPrivateFlags相关的方法是一套固有逻辑不影响用户measure的过程,这里不展开讨论。大家略过这些方法就好。(其实是自己也不太清楚哈哈~~T.T)
看方法,measure其实非常简单,在发现widthMeasureSpec或 heightMeasureSpec发生变化时就去调用onMeasure()方法。大家可以发现measure方法定义成了final类型,设计者在设计之初就不希望这个方法被开发者修改。保证了android初始化view的原理是一致的。
第二个方法 onMeasure();
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
Tips:这是view的三个measure方法中唯一一个不是final的方法。因此我们在自定义空间中最多控制的也是这个方法。将你需要的widthMeasureSpec和heightMeasureSpec直接传过来就能调用setMeasuredDimension()方法进行赋值了。
第三种方法 setMeasuredDimension()
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
Tips:不难看出,mMeasuredWidth和mMeasuredHeight已经赋值成功,证明view的measure完成。是不是非常的简单。下面来了解一下ViewGroup的分发方法。
ViewGroup
方法一measureChildren()
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];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
遍历所有的viewTree下所有个子view.若子View未GONE就去measureChild(),测量对应的view.
方法二measureChild()
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
//获取对应的子view测量出来的宽高。
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
//调用子view的measure方法对mMeasureWidth和mMeasureHeight进行赋值。完成测量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
方法三measureChildWithMargins()
//和方法二measureChild()完全一样的逻辑。只是宽高计算的时候加入了margin.
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
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);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
和方法而逻辑雷同。关键是调用方法四的时候,传入的第二个参数int padding的数值加入了margin.
方法4getChildMeasureSpec()
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//viewGroup的spec
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//父控件的大小减去padding得到子View的大小。
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
//根据Mode以及childDimension获取子view对应的MeasureSpec。
switch (specMode) {
case MeasureSpec.EXACTLY:
//若childDimension有设置具体值,布局具体到具体值。
//若childDimension = match_parent。size赋予测出的大小,mode设置为EXACTLY。
//若childDimension = wrap_content。size赋予测出的大小,mode设置为AT_MOST。
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
// LayoutParams.MATCH_PARENT的大小
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} 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
case MeasureSpec.AT_MOST:
//若childDimension有设置具体值,布局具体到具体值。
//若childDimension = match_parent。size赋予测出的大小,mode设置为AT_MOST。
//若childDimension = wrap_content。size赋予测出的大小,mode设置为AT_MOST。
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} 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;
} 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
case MeasureSpec.UNSPECIFIED:
//若childDimension有设置具体值,布局具体到具体值。
//若childDimension = match_parent。size设置为0,mode设置为UNSPECIFIED。
//若childDimension = wrap_content。size设置为0,mode设置为UNSPECIFIED。
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//返回一个测量好的 MeasureSpec,包含resultSize以及resultMode。
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
Tips: getChildMeasureSpec是测量的关键步奏。他根据childDimension的情况,把测量出的大小和定好Mode。拼接好,并返回给子view去measure。我在代码中进行了必要的注解,大家可以打开源码,多读几遍加深影响。
后记
这里就是view以及viewGroup中measure最主要的方法介绍。了解了这些,当你再读LinearLayout等布局的measure时候就能水到渠成了。希望这篇文章能对大家能有所帮助。
我的下篇博客会针对view绘制的另两个过程 layout以及draw进行分享,
至于LinearLayout具体的计算绘制过程当我有一定心得后也会写博文和大家分享。
楼猪是工作一年的小虾。各位大神们发现问题希望能及时的拍砖。我会学马上改正,虚心学习的~