首先查看 step1 的 drawBackground()
方法,在下面的注释1处,我们可以其对偏移量进行了判断,当偏移量为0的时候,就直接绘制;当偏移量不为0的时候,我们先进行偏移再执行背景的绘制。注意此处执行的 draw
方法是在 Drawable
里面的。
/** * Draws the background onto the specified canvas. * * @param canvas Canvas on which to draw the background */ @UnsupportedAppUsage private void drawBackground(Canvas canvas) { final Drawable background = mBackground; if (background == null) { return; } setBackgroundBounds(); ... final int scrollX = mScrollX; final int scrollY = mScrollY; if ((scrollX | scrollY) == 0) {//1 background.draw(canvas); } else {//2 canvas.translate(scrollX, scrollY); background.draw(canvas); canvas.translate(-scrollX, -scrollY); } } 复制代码
然后我们看 step 3 的 onDraw()
方法,我们发现这是个空实现的方法,在注释处标注了需要自定义 View 的时候实现它。
/** * Implement this to do your drawing. * * @param canvas the canvas on which the background will be drawn */ protected void onDraw(Canvas canvas) { } 复制代码
我们继续看 step 4 的 dispatchDraw()
方法,这也是一个空方法。注释处标明:需要在派生类(继承类)绘制了自身之后,绘制子类之前,对该方法进行重写调用。在 ViewGroup
中,有对这个方法进行重写,我们可以继续到 ViewGroup
查看。
/** * Called by draw to draw the child views. This may be overridden * by derived classes to gain control just before its children are drawn * (but after its own view has been drawn). * @param canvas the canvas on which to draw the view */ protected void dispatchDraw(Canvas canvas) { } 复制代码
在 ViewGroup
中,我们在下列注释1处看到 dispatchDraw()
方法对子元素执行了遍历,在注释2 3 处,可以看到其对每一个子 View 都执行了 drawChild()
方法。
@Override protected void dispatchDraw(Canvas canvas) { ... for (int i = 0; i < childrenCount; i++) {//1 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); } } ... } 复制代码
而点开 drawChild()
方法,我们发现它是直接调用了子 View 中 boolean
类型的 draw()
方法(并非我们上文提到的执行最终绘制的 draw()
方法)
/** * 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 中 boolean
类型的 draw()
方法,在注释1处检测是否无缓存,如果无缓存就直接执行绘制;如果有缓存,就在注释2处利用缓存显示。
/** * This method is called by ViewGroup.drawChild() to have each child view draw itself. * * This is where the View specializes rendering behavior based on layer type, * and hardware acceleration. */ boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) { ... if (!drawingWithDrawingCache) {//1 if (drawingWithRenderNode) { mPrivateFlags &= ~PFLAG_DIRTY_MASK; ((RecordingCanvas) 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) {//2,有缓存 mPrivateFlags &= ~PFLAG_DIRTY_MASK; if (layerType == LAYER_TYPE_NONE || mLayerPaint == null) { // no layer paint, use temporary paint to draw bitmap ... } else { // use layer paint to draw the bitmap, merging the two alphas, but also restore ... } } ... return more; } 复制代码
最后,我们来看 step 6 的 onDrawForeground()
方法,我们可以在下方的注释1处可以看到,它绘制的是 ScrollBar 类型的装饰。并在注释2处调用 foreground.draw()
方法绘制视图内容的上层视图。
/** * Draw any foreground content for this view. * * <p>Foreground content may consist of scroll bars, a {@link #setForeground foreground} * drawable or other view-specific decorations. The foreground is drawn on top of the * primary view content.</p> * * @param canvas canvas to draw into */ public void onDrawForeground(Canvas canvas) { onDrawScrollIndicators(canvas); onDrawScrollBars(canvas);//1 final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null; if (foreground != null) { ... foreground.draw(canvas);//2 } } 复制代码
流程图展示
上文的源码分析到此结束了,笔者按照上面的流程整理出来了一张 Draw
方法的流程图,希望可以帮助大家更好的理解。