前几篇文章分别讲述了View的measure、layout流程,接下来将分析三大流程的最后一步View的绘制流程。测量流程决定了View的大小,布局流程决定了View的位置,那么绘制流程将决定View的样子,一个View该显示什么由绘制流程完成。
从performDraw()开始
private void performDraw() {
//...
final boolean fullRedrawNeeded = mFullRedrawNeeded;
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
//省略...
}
里面又调用了ViewRootImpl的draw方法。
private void draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
//...
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
//省略...
}
/**
* @return true if drawing was successful, false if an error occurred
*/
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
// Draw with software renderer.
final Canvas canvas;
//...
mView.draw(canvas);
//省略...
return true;
}
可以看到最终会调用mView.draw()方法并吧canvas传递过去,canvas可以看成一个画布,View的绘制都是在这块画布上完成的。mView在之前文章中了解到是DecorView。而ViewGroup和FrameLayout都没有重写draw方法,所以我们去看View的draw方法都做了哪些事情
......
/*
* 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
//...
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
//...
// Step 2, save the canvas' layers
//...
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
//...
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
//...
if (drawTop) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, right, top + length, p);
}
//...
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
//省略...
}
谷歌工程师已经帮我们注释好了 整个绘制流程一共分六步。
1、对View的背景进行绘制
2、保存当前的图层信息(可跳过)
3、绘制View的内容
4、对View的子View进行绘制(如果有子View)
5、绘制View的褪色的边缘,类似于阴影效果(可跳过)
6、绘制View的装饰(例如:滚动条)
其中第2步和第5步是可以跳过的,我们这里不做分析。
第一步绘制背景
private void drawBackground(Canvas canvas) {
//获取xml中通过android:background属性或者代码中setBackgroundColor()、setBackgroundResource()等方法进行赋值的背景Drawable
final Drawable background = mBackground;
//...
//根据layout过程确定的View位置来设置背景的绘制区域
if (mBackgroundSizeChanged) {
background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false;
rebuildOutline();
}
//...
//调用Drawable的draw()方法来完成背景的绘制工作
background.draw(canvas);
//省略...
}
第三步绘制自己
/**
* Implement this to do your drawing.
*
* @param canvas the canvas on which the background will be drawn
*/
protected void onDraw(Canvas canvas) {
}
View中该方法是一个空实现,因为不同的View有着不同的内容,这需要我们自己去实现,即在自定义View中重写该方法来实现。
第四步绘制子View
@Override
protected void dispatchDraw(Canvas canvas) {
//...
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
//...
for (int i = 0; i < childrenCount; i++) {
//...
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
//...
// Draw any disappearing views that have animations
if (mDisappearingChildren != null) {
//...
for (int i = disappearingCount; i >= 0; i--) {
//...
more |= drawChild(canvas, child, drawingTime);
}
}
//省略...
}
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
可以看出在dispatchDraw中对所有的子View都进行了遍历绘制
第六步滚动条的绘制
public void onDrawForeground(Canvas canvas) {
onDrawScrollIndicators(canvas);
onDrawScrollBars(canvas);
final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
if (foreground != null) {
if (mForegroundInfo.mBoundsChanged) {
mForegroundInfo.mBoundsChanged = false;
final Rect selfBounds = mForegroundInfo.mSelfBounds;
final Rect overlayBounds = mForegroundInfo.mOverlayBounds;
if (mForegroundInfo.mInsidePadding) {
selfBounds.set(0, 0, getWidth(), getHeight());
} else {
selfBounds.set(getPaddingLeft(), getPaddingTop(),
getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
}
final int ld = getLayoutDirection();
Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),
foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);
foreground.setBounds(overlayBounds);
}
foreground.draw(canvas);
}
}
绘制流程很清晰先确定绘制范围再进行绘制
到此为止View的绘制流程我们已经分析完了。
网友评论