1. 简介
-
View
的绘制过程分为三部分:measure
、layout
、draw
。
measure
用来测量View的宽和高。
layout
用来计算View的位置。
draw
用来绘制View。
-
measure
过程可以查看这篇文章:自定义View原理篇(1)- measure过程。 -
layout
过程可以查看这篇文章:自定义View原理篇(2)- layout过程。 - 本章主要对
draw
过程进行详细的分析。 - 本文源码基于android 27。
2. draw的始点
跟measure
、layout
一样,draw
也是始于ViewRootImpl
的performTraversals()
:
2.1 ViewRootImpl的performTraversals
private void performTraversals() {
//...
//获得view宽高的测量规格,mWidth和mHeight表示窗口的宽高,lp.widthhe和lp.height表示DecorView根布局宽和高
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//执行测量
//...
performLayout(lp, mWidth, mHeight);//执行布局
//...
if (!cancelDraw && !newSurface) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
performDraw();//执行绘制
}
//...
}
再来看看performDraw()
:
2.2 ViewRootImpl的performDraw
private void performDraw() {
//...
final boolean fullRedrawNeeded = mFullRedrawNeeded;
draw(fullRedrawNeeded);
//...
}
下面重点来分析draw
过程。
3.draw过程分析
draw
,顾名思义,就是来绘制View
。
同样,draw
过程根据View
的类型也可以分为两种情况:
- 绘制单一
View
时,只需View
本身即可;- 绘制
ViewGroup
时,不仅需要绘制ViewGroup
本身,还需绘制其所有的子View
。
我们对这两种情况分别进行分析。
3.1 单一View的draw过程
3.1.1 View的draw
单一View
的draw
过程是从View
的draw()
方法开始:
public void draw(Canvas canvas) {
//...
/*
* 绘制流程如下:
*
* 1. 绘制view背景
* 2. 如果有需要,就保存图层
* 3. 绘制view内容
* 4. 绘制子View
* 5. 如果有必要,绘制渐变框和恢复图层
* 6. 绘制装饰(滑动条等)
*/
if (!dirtyOpaque) {
drawBackground(canvas);//步骤1. 绘制view背景
}
// 如果可能的话,跳过第2步和第5步(常见情况)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
if (!dirtyOpaque) onDraw(canvas);//步骤3. 绘制view内容
dispatchDraw(canvas);//步骤4. 绘制子View
//...
onDrawForeground(canvas);//步骤6. 绘制装饰(滑动条等)
//...
// 绘制完成,返回
return;
}
//如果有需要,会执行第2步和第5步
//...
//步骤2. 保存图层
if (solidColor == 0) {
if (drawTop) {
//保存图层
canvas.saveLayer(left, top, right, top + length, null, flags);
}
//...
}
if (!dirtyOpaque) onDraw(canvas);//步骤3. 绘制view内容
dispatchDraw(canvas);//步骤4. 绘制子View
//步骤5. 绘制渐变框和恢复图层
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);
}
//...
//恢复图层
canvas.restoreToCount(saveCount);
//...
onDrawForeground(canvas);//步骤6. 绘制装饰(滑动条等)
//...
}
可以看到,中间绘制时可能会跳过第2步和第5步,这样可以提高绘制的效率。
下面我们来分析一下drawBackground()
、onDraw()
、dispatchDraw()
、 onDrawForeground()
等方法,即步骤1、3、4、6。
3.1.2 View的drawBackground
首先来看看drawBackground()
,这个方法是用来绘制背景:
private void drawBackground(Canvas canvas) {
// 获取背景 drawable
final Drawable background = mBackground;
if (background == null) {
return;
}
// 根据在 layout 过程中获取的 View 的位置参数,来设置背景的边界
setBackgroundBounds();
//硬件加速渲染
if (canvas.isHardwareAccelerated() && mAttachInfo != null
&& mAttachInfo.mThreadedRenderer != null) {
mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);
final RenderNode renderNode = mBackgroundRenderNode;
if (renderNode != null && renderNode.isValid()) {
setBackgroundRenderNodeProperties(renderNode);
((DisplayListCanvas) canvas).drawRenderNode(renderNode);
return;
}
}
// 获取 mScrollX 和 mScrollY值
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
// 调用 Drawable 的 draw 方法绘制背景
background.draw(canvas);
} else {
// 若 mScrollX 和 mScrollY 有值,则对 canvas 的坐标进行偏移
canvas.translate(scrollX, scrollY);
// 调用 Drawable 的 draw 方法绘制背景
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
3.1.3 View的onDraw
由于 View
的内容各不相同,因此onDraw()
方法在View
类中是个空实现,具体的View
(如TextView
等)需对其进行重写,有兴趣的可以去看看TextView
等的onDraw()
实现。
protected void onDraw(Canvas canvas) {
//具体内容的绘制逻辑
}
3.1.4 View的dispatchDraw
由于单一View
没有子View
,所以其dispatchDraw()
方法是个空实现:
protected void dispatchDraw(Canvas canvas) {
}
3.1.5 View的onDrawForeground
onDrawForeground()
方法就是用来绘制一些装饰,比如滑动指示器、滑动条、前景等:
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);
}
}
3.1.6 单一View的draw过程流程图
来张流程图简单总结一下:
3.2 ViewGroup的draw过程
ViewGroup
的draw
过程同样是从View
的draw()
方法开始,ViewGroup
没有重写draw()
方法,所以跟View
的draw()
代码是一样,其drawBackground()
、onDraw()
、 onDrawForeground()
等方法实现也一样,这里就不重述了,我们来看下唯一不同的地方:dispatchDraw()
。
3.2.1 ViewGroup的dispatchDraw
@Override
protected void dispatchDraw(Canvas canvas) {
//子View数量
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
//...
//遍历子View
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
//绘制子View
more |= drawChild(canvas, transientChild, drawingTime);
}
//...
}
//...
}
我们再来看看drawChild()
方法:
3.2.2 ViewGroup的drawChild
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
drawChild()
方法中就是调用了子View
的draw()
去绘制子View
.
3.2.3 ViewGroup的draw过程流程图
来张流程图简单总结一下:
4. 自定义View
4.1 自定义单一view
自定义单一View
需要重写onDraw()
:
@Override
protected void onDraw(Canvas canvas) {
//具体内容的绘制逻辑
}
4.2 自定义ViewGroup
自定义的ViewGroup
一般是作为容器来放置各种子View
的,所以一般无需重写onDraw()
。
因此ViewGroup
默认启用了WILL_NOT_DRAW
这个标志位,启用这个标记位后系统会进行相应的优化。
所以,当我们有特殊需求需要重写ViewGroup
的onDraw()
时,应当关闭这个标记位。可以通过调用setWillNotDraw(false)
来关闭它。