背景
这几天,我阅读了一下三大常见布局(线性,相对和框架)的测量、布局流程,而这三者的绘制过程,都是在View.draw()方法中实现的,所以我就阅读了一下View的draw(canvas)方法。
由于我只是想了解常见布局的绘制流程,所以一些特殊情况和多余步骤我就没有去看,这不是读小说散文,要带有目的性。
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); // 这个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()方法
View#drawBackground()
代码如下
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); } } };
ViewGroup#dispatchDraw
回到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树绘制的开始