在上一篇文章中,我们得知了View是如何测量的,下面去看一下布局和测绘的流程。
进入ViewRootImpl的performTraversals()方法
private void performTraversals() {
//...
performLayout(lp, mWidth, mHeight);
//...
}
进入performLayout()方法,
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
//...
final View host = mView;
if (host == null) {
return;
}
//...
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
//...
}
这里将mView赋值给host对象,而mView是DecorView对象,继承于FrameLayout,调用layout()方法,并传递DecorView的宽高,进入layout()方法,发现其调用了 onLayout(changed, l, t, r, b)方法,在点进去发现该方法是一个空方法,留给子类去实现的
public void layout(int l, int t, int r, int b) {
//...
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) {
onLayout(changed, l, t, r, b);
}
//...
}
这里补充一点,在上述setFrame()方法中,确定了成员变量mLeft、mTop、mBottom、mRight的值,从而确定了控件位置。
由于DecorView是继承于FrameLayout,进入FrameLayout中,寻找onLayout(),发现其调用了layoutChildren(),这里实际上是对子View进行摆放的操作,如计算view的padding值、margin值以及Gravity等,在for循环末端调用 child.layout(),对layout()进行递归操作,从而完成view的布局。
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
ViewGroup的布局
1)layout 确定自身的位置
2)onLayout确定子View的位置
View的布局
1)layout 确定自身的位置
下面再来看一下View的绘制
进入ViewRootImpl的performDraw()方法,调用了draw()方法,然后又调用了drawSoftware()方法,在该方法中调用了 mView.draw(canvas)方法,进入该draw方法,发现其调用了View的draw()方法
public void draw(Canvas canvas) {
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
// we're done...
return;
}
}
到这里,发现view的draw方法主要包括6个步骤
* 1. Draw the background 绘制背景 drawBackground()
* 2. If necessary, save the canvas’ layers to prepare for fading 保存图层
* 3. Draw view’s content 绘制自己 onDraw()
* 4. Draw children 绘制子view,如果该View是容器的话 dispatchDraw()
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance) 绘制装饰,如滚动条等 onDrawForeground()
在绘制子View的时候,需要调用dispatchDraw()方法,这里去查看ViewGroup中是如何实现绘制子View的,进入dispatchDraw()方法,发现再代码中,利用for循环调用了drawChild()方法
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
这里最终调用了子View的draw()方法。
ViewGroup的绘制(主要步骤)
1)drawBackground()
2)onDraw()
3)dispatchDraw()
4)onDrawForeground()
View的绘制(主要步骤)
1)drawBackground()
2)onDraw()
3)onDrawForeground()
那么在自定义View的时候,如果该View是一个容器,那么需要实现其onMeasure,onLayout,onDraw方法,如果不是,则需要实现其onMeasure和onDraw方法,其中onDraw方法可选。