根据前面分析的view的测量和布局流程都是从ViewRootImpl的PerforTraversals方法开始分析的,view的draw流程我们依旧从此看起
View的draw流程
ViewRootImpl#performDraw()
//...
final boolean fullRedrawNeeded = mFullRedrawNeeded || mReportNextDraw;
mFullRedrawNeeded = false;
//...
try {
boolean canUseAsync = draw(fullRedrawNeeded);
if (usingAsyncReport && !canUseAsync) {
mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
usingAsyncReport = false;
}
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
//省略...
performDraw方法调用了draw方法去绘制视图,其中传入了参数fullRedrawNeeded,该参数是由mFullRedrawNeeded和mReportNextDraw参数获取到的,它的作用是判断是否需要重新绘制全部视图。如果是第一次绘制视图,当然需要全部绘制;如果不是第一次,就没有必要全部都再绘制一遍。
ViewRootImpl#draw()
draw方法比较长,我们只看关键的那一部分:
//...
//获取mDirty的值,该值表示需要重绘的区域
final Rect dirty = mDirty;
if (mSurfaceHolder != null) {
// The app owns the surface, we won't draw.
dirty.setEmpty();
if (animating && mScroller != null) {
mScroller.abortAnimation();
}
return false;
}
//如果fullRedrawNeeded为真,则把mDirty区域置为整个屏幕,表示整个视图都需要绘制
//第一次绘制流程,需要绘制所有的视图
if (fullRedrawNeeded) {
mAttachInfo.mIgnoreDirtyState = true;
dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
}
//...
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
首先获取到mDirty值,该值保存了需要重新绘制的区域信息。接着根据fullRedrawNeeded来判断是否需要重置dirty区域为整个屏幕。最后调用ViewRootImpl#drawsoft方法,把相关参数传进去。
ViewRootImpl#drawSoftware
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
// Draw with software renderer.
final Canvas canvas;
try {
final int left = dirty.left;
final int top = dirty.top;
final int right = dirty.right;
final int bottom = dirty.bottom;
//锁定canvas区域,由dirty区域决定
canvas = mSurface.lockCanvas(dirty);
// The dirty rectangle can be modified by Surface.lockCanvas()
//noinspection ConstantConditions
if (left != dirty.left || top != dirty.top || right != dirty.right
|| bottom != dirty.bottom) {
attachInfo.mIgnoreDirtyState = true;
}
canvas.setDensity(mDensity);
}
try {
if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
dirty.setEmpty();
mIsAnimating = false;
attachInfo.mDrawingTime = SystemClock.uptimeMillis();
mView.mPrivateFlags |= View.PFLAG_DRAWN;
try {
canvas.translate(-xoff, -yoff);
if (mTranslator != null) {
mTranslator.translateCanvas(canvas);
}
canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
attachInfo.mSetIgnoreDirtyState = false;
//正式开始绘制
mView.draw(canvas);
}
}
return true;
}
这里首先实例化了canvas对象,然后锁定该canvas的区域,由dirty区域决定,接着对canvas进行了一系列的属性赋值,最后调用了mView.draw(canvas)方法。从performTraversals方法的源码中可以知道mView其实就是DecorView,也就是说从DecorView开始绘制,前面都是准备工作,现在才开始真正的绘制流程。
View#draw()
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* 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;
//dirtyOpaque判断当前veiw是否是透明的,如果view是透明的。那么view的背景、自己本身就没有必要绘制了
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);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
//...
}
draw方法比较长,但是源码中为我们写的很清楚,draw方法主要有六大步骤:
- 绘制背景
- 保存画布图层以准备淡化
- 绘制视图内容
- 绘制当前view的子view
- 绘制淡化边缘并回复图层
- 绘制装饰(例如滚动条)
这里省略第二步和第五步不看
下面看一下这四大步骤:
View#drawBackground()
private void drawBackground(Canvas canvas) {
//背景其实是一个drawable,就像我们平常自己写的.xml背景图一样也是一个drawable
final Drawable background = mBackground;
if (background == null) {
return;
}
//设置背景边界
setBackgroundBounds();
// ...
//获取当前view的mScrollX和mScrollY值
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
//如果scrollX和scrollY有值,则对canvas的坐标进行偏移,再绘制背景
canvas.translate(scrollX, scrollY);
background.draw(canvas);
//绘制完后,将canvas移回原位置
canvas.translate(-scrollX, -scrollY);
}
}
如果有背景需要绘制,则在画布上先绘制背景。
View#draw()
protected void onDraw(Canvas canvas) {
}
draw方法是空方法,因为不同的view有不同的样子,当我们自定义view的时候需要自己去绘制。
ViewGroup#dispatchDraw
view中该方法是空方法,而ViewGroup继承了View,所以这里去看看ViewGroup的dispatchDraw:
protected void dispatchDraw(Canvas canvas) {
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
//...
//循环遍历子view,调用drawChild方法绘制子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) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
}
方法很长,但可以看到这里有和layout相似的想法,调用drawChild方法去绘制view的子view。
View#drawChild()
drawChild()调用draw方法。这个方法主要是画ViewGroup的孩子view,他负责获取画布当前的状态,当画布被被裁剪,平移等操作时以便子view的起始坐标为(0,0)和有动画时
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
View#draw(canvas,this,drawing)
这个方法使每个子视图自己绘制,是view专门根据图层类型(cpu 还是 gpu)进行渲染和硬件加速。
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
//判断此view是否开启硬件加速
final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
/* If an attached view draws to a HW canvas, it may use its RenderNode + DisplayList.
*
* If a view is dettached, its DisplayList shouldn't exist. If the canvas isn't
* HW accelerated, it can't handle drawing RenderNodes.
*/
//源码中有这样一段话,意思大概是如果把view画到画布上,必须要有RenderNode + DisplayList
//判断当前view是否支持硬件加速绘制
boolean drawingWithRenderNode = mAttachInfo != null
&& mAttachInfo.mHardwareAccelerated
&& hardwareAcceleratedCanvas;
//...
//下面这一部分主要就是判断view是cpu绘制还是gpu绘制
//硬件加速绘制用到的绘制节点
RenderNode renderNode = null;
//cpu绘制用到的绘制缓存
Bitmap cache = null;
//获取当前View的绘制类型 LAYER_TYPE_NONE LAYER_TYPE_SOFTWARE LAYER_TYPE_HARDWARE
int layerType = getLayerType();
//如果是cpu绘制类型 也就是带software的标志
if (layerType == LAYER_TYPE_SOFTWARE || !drawingWithRenderNode) {
if (layerType != LAYER_TYPE_NONE) {
layerType = LAYER_TYPE_SOFTWARE;
//开始cpu绘制缓存构建
buildDrawingCache(true);
}
//获得cpu绘制缓存结果 存储在Bitmap中
cache = getDrawingCache(true);
}
//支持硬件加速
if (drawingWithRenderNode) {
//更新gpu绘制列表 保存在RenderNode中
renderNode = updateDisplayListIfDirty();
if (!renderNode.isValid()) {
renderNode = null;
//gpu绘制失败标识
drawingWithRenderNode = false;
}
}
//...
//cpu绘制成功并且gpu绘制失败了
final boolean drawingWithDrawingCache = cache != null && !drawingWithRenderNode;
//...
//走gpu绘制
if (!drawingWithDrawingCache) {
if (drawingWithRenderNode) {//有硬件加速的支持
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
//gpu绘制收集到的DisplayList
((DisplayListCanvas) canvas).drawRenderNode(renderNode);
}else{
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
//没有内容不需要绘制自己,就直接向下分发绘制子view
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
} else {
//继续六大流程,绘制自己后再分绘制子view
draw(canvas);
}
}
}else if((cache != null){//走cpu绘制且cpu绘制缓存不为null
//...
//把存储cpu绘制缓存的Bitmap用canvas走cpu绘制
canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
}
return more;
}
当ViewGroup向下分发绘制子view的时候,会根据是否开启硬件加速和view的绘制类型来判断view是用cpu绘制还是gpu绘制,gpu的主要作用就是渲染图形,绘制图形的速度会高于cpu,但是gpu会比cpu更加耗电。之前在简单的绘制一个气泡图形的时候,运行后图形展示不出来,网上给出的解决方法就是开启硬件加速,看了这里的源码知道了开启硬件加速绘制的大概流程。
开启硬件加速的源码不止这里还有,以后会慢慢学习。一篇很好的理解硬件加速的博客
View#onDrawForeground
public void onDrawForeground(Canvas canvas) {
onDrawScrollIndicators(canvas);
onDrawScrollBars(canvas);
final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
if (foreground != null) {
//先设定绘制区域
//...
//利用canvas进行绘制
foreground.draw(canvas);
}
}
总结
- 准备工作:ViewRootImp中
- performTravsals() ---> performDraw()
- performDraw() ---> draw() 确定绘制的区域mDirty
- drawSoftware() 创建Canvas对象,进行真正的绘制
- 开始绘制的重要四大流程
- drawBackground(canvas)
- onDraw(canvas) 空方法 需要重写
- dispatchDraw(canvas) 绘制child
- onDrawForeground(canvas) 滚动条绘制