view的绘制是一层一层向下迭代而来的,DecorView->ViewGroup->View,按照这个流程从上往下,依次measure、layout、draw。
小知识
- MeasureSpec
前两位是测量模式,后30位是测量的尺寸大小
测量模式:1. UNSPECIFIED(父容器对子容器没有任何限制,要多大给多大,主要用于scrollview或系统内调用) - EXACTLY(对应march_parent或具体的数值)
-
AT_MOST(wrap_content,包裹内容但不超过父view的尺寸)
子View的大小由父类的measureSpec和子View的layoutparams属性共同决定。
如果父view是EXACTLY,子View是match_parent,那么子view最终大小是EXACTLY,大小为父view的剩余空间。
总结就是,父view的模式无论如何,子View如果是具体大小,mode就是EXACTLY+子View设置的大小。父View是EXACTLY,子View是什么就是什么(ziView为warp_content就是AT_MOST,ziView是match_parent就是EXACTLY),fuView如果是AT_MOST或者UNSPECIFIED,子view不是具体数值的情况下和父View保持一致,
一、单独一个view的measure
根据measureSpec和建议的size计算view的宽高并存储
- measure方法调用了onMeasure方法
// 根据measureSpec计算宽高,并存储该值
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// setMeasuredDimension必须在onMeasure方法中调用去存储测量的宽高
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
getDefaultSize
public static int getDefaultSize(int size, int measureSpec) {
int result = size; // 默认的大小
int specMode = MeasureSpec.getMode(measureSpec); // 获得测量模式
int specSize = MeasureSpec.getSize(measureSpec); // 获得measureSpec后三十位的大小
// 根据模式计算大小
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
getSuggestedMinimumWidth
//无背景,则设置的最小宽度,否则为背景图Drawable的宽度和设置的最小宽度的最大值。没有设置minWidth,则默认为0
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
二、viewGroup的measure
abstract viewGroup没有重写onMeasure方法的,这里以FrameLayout的onMeasure为例,因为width和height流程是一样的,这里只显示width相关核心代码
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
// wrap_content
int width = 0;
int lineWidth = 0;
int cCount = getChildCount();
for (int i = 0; i < cCount; i++) {
// 测量childView的宽高,并计算出父view应该的宽高。
measureChild(child, widthMeasureSpec, heightMeasureSpec);
MarginLayoutParams lp = (MarginLayoutParams) child
.getLayoutParams();
// childView在布局中占得总宽度
int childWidth = child.getMeasuredWidth() + lp.leftMargin
+ lp.rightMargin;
if (lineWidth + childWidth > sizeWidth - getPaddingLeft() - getPaddingRight()) {
// 如果当前childView的宽+之前childview的总宽度 > 父view剩余空间
width = Math.max(width, lineWidth);
lineWidth = childWidth;
} else {
lineWidth += childWidth;
lineHeight = Math.max(lineHeight, childHeight);
}
if (i == cCount - 1) {
width = Math.max(lineWidth, width);
height += lineHeight;
}
}
setMeasuredDimension(
modeWidth == MeasureSpec.EXACTLY ? sizeWidth : width + getPaddingLeft() + getPaddingRight(),
modeHeight == MeasureSpec.EXACTLY ? sizeHeight : height + getPaddingTop() + getPaddingBottom()//
);
measureChild,这里根据父view的measureSpec和ziview的layoutparams测量子view
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
// 获得子view的MeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
// 调用view的measure方法
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
viewGroup的measure总结就是,重写onmeasure方法,onmeasure方法中,会根据measureSpec获得测量模式和宽高,接下来会循环测量每一个子view的宽高,并根据子view的宽高和子view的margin的总值设置父view的大小。子view的宽高确定根据父view的measurespec和子view的layoutparams设置。child的测量方法是measureChild,该方法调用了子view的measure方法,然后是onmeasure方法。
三、layout
该layout方法用于确定View本身的位置,即设置View本身的四个顶点位置.
viewGroup虽然重写了layout方法,但是只是在调super.layout之前,先看是否执行动画。
所以可以认为layout方法是一样的。
public void layout(int l, int t, int r, int b) {
int oldL = mLeft;
.......
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//若视图的大小 或者位置发生变化 会重新确定该View所有的子View在父容器的位置:onLayout()
onLayout(changed, l, t, r, b);
}
}
setFrame
boolean changed = false;
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
// 如果改变的话
changed = true;
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
// Invalidate our old position
invalidate(sizeChanged);
mLeft = left;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
if (sizeChanged) {
sizeChange(newWidth, newHeight, oldWidth, oldHeight);
}
if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {
invalidate(sizeChanged);
invalidateParentCaches();
}
}
return changed;
四、onLayout方法,因为view的onlayout方法是个空方法,所以用Linearlayout的onLayout方法
/**
* @param changed: This is a new size or position for this view
* @param l: Left position, relative to parent
*/
protected void onLayout(boolean changed, int l, int t, int r, int b) {
lineViews.clear();
int cCount = getChildCount();
for (int i = 0; i < cCount; i++) {
// 最终会走到
child.layout(lc, tc, rc, bc);
}
}
总结一下, layout会根据view的四个顶点的位置invalidate,layout中调用onlayout方法,onlayout方法调用child.layout设置子view在父view的位置。
五、draw
draw方法有7个步骤说明,这里简单翻译一下
1.绘制背景色
- 如果需要,为了fading保存图层
- 绘制view的内容
- 绘制children
- 如果需要,绘制fading边界并重建图层
- 绘制装饰(如滚动条)
- 如果需要,绘制默认的focus highlight
public void draw(Canvas canvas) {
drawBackground(canvas); //绘制背景色
onDraw(canvas); // 绘制本身view内容
dispatchDraw(canvas); // 绘制子view
onDrawForeground(canvas); //绘制装饰
}
viewgroup没有重写draw方法,view没有childview,所以dispatchDraw是空方法,因此这里用viewGroup的dispatchdraw。
protected void dispatchDraw(Canvas canvas) {
final View[] children = mChildren;
for (int i = 0; i < childrenCount; i++) {
final View child = children[i];
drawChild(canvas, transientChild, drawingTime); //绘制子view
}
}
总结,自定义viewgroup需要重写ondraw和dispatchDraw,自定义view重写onDraw方法。
viewgroup会绘制自身(包含背景、内容),然后遍历子view绘制所有子view,之后会绘制滚动条等装饰。
getMeasuredWidth()与getWidth()区别
getMeasureWidth()方法在measure()过程结束后就可以获取到了,
而getWidth()方法要在layout()过程结束后才能获取到。
getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,而getWidth()方法中的值则是通过layout(left,top,right,bottom)方法设置的。