调用了View#drawBackground方法,我们看一下源码:
private void drawBackground(Canvas canvas) { //mBackground是该View的背景参数,比如背景颜色 final Drawable background = mBackground; if (background == null) { return; } //根据View四个布局参数来确定背景的边界 setBackgroundBounds(); // Attempt to use a display list if requested. if (canvas.isHardwareAccelerated() && mAttachInfo != null && mAttachInfo.mHardwareRenderer != null) { mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode); final RenderNode renderNode = mBackgroundRenderNode; if (renderNode != null && renderNode.isValid()) { setBackgroundRenderNodeProperties(renderNode); ((DisplayListCanvas) canvas).drawRenderNode(renderNode); return; } } //获取当前View的mScrollX和mScrollY值 final int scrollX = mScrollX; final int scrollY = mScrollY; if ((scrollX | scrollY) == 0) { background.draw(canvas); } else { canvas.translate(scrollX, scrollY); background.draw(canvas); canvas.translate(-scrollX, -scrollY); } }不是重点,我们记住这个步骤,往下看。
绘制View的内容信息 /** * Implement this to do your drawing. * * @param canvas the canvas on which the background will be drawn */ protected void onDraw(Canvas canvas) { }View#onDraw是一个空方法,就如我们上一个篇分析onLayout()一样,由于不同的View有着不同的布局,所以在视图的绘制过程也同样有所不同,这需要我们View的子类按照自己的需求去重写onDraw()方法来实现。
绘制子View因为是绘制子View,那么我们可以在View找不到这个方法,在FrameLayout中方法查找是指向ViewGroup#dispatchDraw的方法,由于这个方法实现主要是遍历所以子View,每个子View调用drawChild。其实ViewGroup#dispatchDraw方法实现满足了我们很多ViewGroup的子类,如LinearLayout、FrameLayout都是没有重写dispatchDraw方法的,如果我们自定义View需求比较特殊,可以重写该方法。 另外ViewGroup#dispatchDraw方法代码过多,我们直接圈出重点ViewGroup#drawChild:
/** * Draw one child of this View Group. This method is responsible for getting * the canvas in the right state. This includes clipping, translating so * that the child's scrolled origin is at 0, 0, and applying any animation * transformations. * * @param canvas The canvas on which to draw the child * @param child Who to draw * @param drawingTime The time at which draw is occurring * @return True if an invalidate() was issued */ protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime); }子类View的draw()方法,主要方法的重载,跟我们上面分析的draw(Canvas 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) { mPrivateFlags &= ~PFLAG_DIRTY_MASK; dispatchDraw(canvas); } else { draw(canvas); } } } else if (cache != null) { mPrivateFlags &= ~PFLAG_DIRTY_MASK; if (layerType == LAYER_TYPE_NONE) { // 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(); mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha)); canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint); mLayerPaint.setAlpha(layerPaintAlpha); } } ··· }从一开始的判断的对象命名来理解,判断时候有绘制缓存,应该就是是否绘制过了,否的话将会调用draw(canvas)方法。而我们上面分析过,drawChild()的核心过程就是为子视图分配的cavas画布绘制区,在设置了一些位置、动画等参数和一个Flag标记后就会调用子视图的draw()函数进行具体的绘制了。
我们总体可以这样理解ViewGroup的绘制过程,遍历子View,重载draw方法对子View进行绘制,而子View又会调用自身的draw方法绘制自己,通过不断遍历子View及子View的不断对自身的绘制,从而使得View树完成绘制。(该方法实现过程较为复杂抽象,能力有限在参考下截取这部分重点来说说,有需要大家可以结合其他分析进行自我总结归纳。)
绘制装饰绘制装饰,其实对View的非背景、View内容的部分,如滚动条等等,我们看看View#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); } }跟普通的绘制过程较为相似,先设定可绘制的区域,然后使用Canvas进行绘制,有兴趣的同学可细分下去了解。
大家可以配合下面这图进行理解和总结。