这几天,我阅读了一下三大常见布局(线性,相对和框架)的测量、布局流程,而这三者的绘制过程,都是在View.draw()方法中实现的,所以我就阅读了一下View的draw(canvas)方法。
由于我只是想了解常见布局的绘制流程,所以一些特殊情况和多余步骤我就没有去看,这不是读小说散文,要带有目的性。
主要代码如下
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState); // 这个view是否不透明
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;
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); // 调用onDraw()方法绘制view,这个方法要子类自己去实现
// Step 4, draw the children
dispatchDraw(canvas); // 主要调用ViewGroup.dispatchDraw()方法
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;
}
...
}
这里我主要看了drawBackground()和dispatchDraw()方法
代码如下
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
if (background == null) {
return;
}
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;
}
}
// 如果发生滚动,计算偏移量
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
canvas.translate(scrollX, scrollY); // 直接调用了native方法,应该是对canvas进行坐标偏移
background.draw(canvas); // 绘制背景,可见我们在滚动view的时候,背景不是一直不变的,而是一直在绘制
canvas.translate(-scrollX, -scrollY); // 再把canvas复原
}
}
又调用了setBackgroundBounds()方法,代码如下
void setBackgroundBounds() {
if (mBackgroundSizeChanged && mBackground != null) { // 背景尺寸变化才会重新设置边界
mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop); // 设置背景图边界是整个view
mBackgroundSizeChanged = false;
rebuildOutline(); // 设置outline轮廓
}
}
其间,又调用了rebuildOutline()方法,代码如下
private void rebuildOutline() {
// Unattached views ignore this signal, and outline is recomputed in onAttachedToWindow()
if (mAttachInfo == null) return;
if (mOutlineProvider == null) {
// no provider, remove outline
mRenderNode.setOutline(null);
} else { // 有provider,清空outline,并设置轮廓不透明
final Outline outline = mAttachInfo.mTmpOutline;
outline.setEmpty();
outline.setAlpha(1.0f);
mOutlineProvider.getOutline(this, outline); // ViewOutlineProvider.getOutline()
mRenderNode.setOutline(outline); // 这直接调用了个native方法,直接忽略
}
}
这里的关键方法是ViewOutlineProvider.getOutline(),代码如下
public static final ViewOutlineProvider BACKGROUND = new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
Drawable background = view.getBackground();
if (background != null) { // 从背景获取轮廓
background.getOutline(outline);
} else { // 没有背景就把轮廓设为全透明
outline.setRect(0, 0, view.getWidth(), view.getHeight());
outline.setAlpha(0.0f);
}
}
};
回到View.draw()方法,继续看第二个关键方法dispatchDraw(),这个方法是在ViewGroup类中实现的,代码非常长,但最关键的是下面这么几行
@Override
protected void dispatchDraw(Canvas canvas) {
boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
int flags = mGroupFlags;
for (int i = 0; i < childrenCount; i++) {
...
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex); // 相当于children[i]
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
...
}
主要调用了drawChild()方法,代码如下
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
又调用了View的draw(canvas,parent,drawingTime)方法,这个方法不是上面说的draw(canvas)方法,参数不一样。它的源码也很长,处理很多像动画、硬件加速等东西,跟正常绘制有关的是下面几行
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
...
if (!drawingWithDrawingCache) { // 不用缓存,直接画
if (drawingWithRenderNode) { // 是否硬件加速
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
((DisplayListCanvas) canvas).drawRenderNode(renderNode);
} else {
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { // 如果当前子view不需要绘制
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas); // 绘制子view的子view
} else {
draw(canvas); // 绘制子view自身
}
}
} else if (cache != null) { // 有缓存的话,绘制bitmap。看来view的缓存是做为bitmap保存的?
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
if (layerType == LAYER_TYPE_NONE || mLayerPaint == null) {
// no layer paint, use temporary paint to draw bitmap
Paint cachePaint = parent.mCachePaint;
if (cachePaint == null) {
cachePaint = new Paint();
cachePaint.setDither(false);
parent.mCachePaint = cachePaint;
}
cachePaint.setAlpha((int) (alpha * 255));
canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
} else {
// use layer paint to draw the bitmap, merging the two alphas, but also restore
int layerPaintAlpha = mLayerPaint.getAlpha();
if (alpha < 1) {
mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
}
canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
if (alpha < 1) {
mLayerPaint.setAlpha(layerPaintAlpha);
}
}
}
...
}
正常的view绘制流程大体就是这样,从View.draw(canvas)到View.onDraw(),再到ViewGroup.dispatchDraw(),如果有子view的话,最后到View.draw(canvas,parent,drawingTime),view利用递归来完成view树的绘制
是否可以这么认为,View.draw(canvas)启动View自己的绘制流程,View.onDraw()则是绘制view本身,ViewGroup.dispatchDraw()则是把绘制过程传递给子view,draw(canvas,parent,drawingTime)开启子view的绘制流程
另外,如果布局是根view,它的draw(canvas)方法在哪儿调用呢?答案是在ViewRootImpl.drawSoftware()方法中被调用,而ViewRootImpl.drawSoftware()方法则被ViewRootImpl.performDraw()调用,而这个performDraw()方法,就是view树绘制的开始