前言

在上一篇我们了解了Activity的构成后,接下来我们开始了解一下View的工作流程,就是measure、layout和draw。measure用来测量View的宽高,layout用来确定View的位置,draw则用来绘制View。接下来我们来看看具体绘制的流程以及,paint和Canvas在这中间所扮演的角色。

1.绘制流程

上一篇我们提到了在performTraversals当中一次调用了performMeasure,performLayout,performDraw方法。 接下来我们了解一下draw具体干嘛,那么我们看到ViewRootImpl. performDraw方法看下他是如何完成具体绘制的。

performTraversals
// Remember if we must report the next draw.
if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
    mReportNextDraw = true;
}

boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() ||viewVisibility != View.VISIBLE;if (!cancelDraw && !newSurface) {
    if (!skipDraw || mReportNextDraw) {
        if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
            for (int i = 0; i < mPendingTransitions.size(); ++i) {
                mPendingTransitions.get(i).startChangingAnimations();}
            mPendingTransitions.clear();}

        performDraw();}
} else {
    if (viewVisibility == View.VISIBLE) {
        // Try again  onMeasure会调用两次的原因
        scheduleTraversals();} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
        for (int i = 0; i < mPendingTransitions.size(); ++i) {
            mPendingTransitions.get(i).endChangingAnimations();}
        mPendingTransitions.clear();}
}

mIsInTraversal = false;

接着看performDraw具体做了什么:

private void performDraw() {
    if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
        return;}

    final boolean fullRedrawNeeded = mFullRedrawNeeded;mFullRedrawNeeded = false;mIsDrawing = true;Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");try {
        draw(fullRedrawNeeded);} finally {
        mIsDrawing = false;Trace.traceEnd(Trace.TRACE_TAG_VIEW);}

    // For whatever reason we didn't create a HardwareRenderer, end any
    // hardware animations that are now dangling
    if (mAttachInfo.mPendingAnimatingRenderNodes != null) {
        final int count = mAttachInfo.mPendingAnimatingRenderNodes.size();for (int i = 0; i < count; i++) {
            mAttachInfo.mPendingAnimatingRenderNodes.get(i).endAllAnimators();}
        mAttachInfo.mPendingAnimatingRenderNodes.clear();}

    if (mReportNextDraw) {
        mReportNextDraw = false;if (mAttachInfo.mHardwareRenderer != null) {
            mAttachInfo.mHardwareRenderer.fence();}

        if (LOCAL_LOGV) {
            Log.v(TAG, "FINISHED DRAWING: " + mWindowAttributes.getTitle());}
        if (mSurfaceHolder != null && mSurface.isValid()) {
            mSurfaceHolderCallback.surfaceRedrawNeeded(mSurfaceHolder);SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();if (callbacks != null) {
                for (SurfaceHolder.Callback c : callbacks) {
                    if (c instanceof SurfaceHolder.Callback2) {
                        ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(
                                mSurfaceHolder);}
                }
            }
        }
        try {
            mWindowSession.finishDrawing(mWindow);} catch (RemoteException e) {
        }
    }
}
这里面又调用了ViewRootImpl---->draw方法,并传递了fullRedrawNeeded参数,而该参数由mFullRedrawNeeded成员变量获取,它的作用是判断是否需要重新绘制全部视图,如果是第一次绘制视图,那么显然应该绘制所以的视图,如果由于某些原因,导致了视图重绘,那么就没有必要绘制所有视图。来看看ViewRootImpl---->draw
private void draw(boolean fullRedrawNeeded) {
    Surface surface = mSurface;if (!surface.isValid()) {
        return;}

    if (DEBUG_FPS) {
        trackFPS();}

    if (!sFirstDrawComplete) {
        synchronized (sFirstDrawHandlers) {
            sFirstDrawComplete = true;final int count = sFirstDrawHandlers.size();for (int i = 0; i< count; i++) {
                mHandler.post(sFirstDrawHandlers.get(i));}
        }
    }

    scrollToRectOrFocus(null, false);if (mAttachInfo.mViewScrollChanged) {
        mAttachInfo.mViewScrollChanged = false;mAttachInfo.mTreeObserver.dispatchOnScrollChanged();}

    boolean animating = mScroller != null && mScroller.computeScrollOffset();final int curScrollY;if (animating) {
        curScrollY = mScroller.getCurrY();} else {
        curScrollY = mScrollY;}
    if (mCurScrollY != curScrollY) {
        mCurScrollY = curScrollY;fullRedrawNeeded = true;if (mView instanceof RootViewSurfaceTaker) {
            ((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);}
    }

    final float appScale = mAttachInfo.mApplicationScale;final boolean scalingRequired = mAttachInfo.mScalingRequired;int resizeAlpha = 0;if (mResizeBuffer != null) {
        long deltaTime = SystemClock.uptimeMillis() - mResizeBufferStartTime;if (deltaTime < mResizeBufferDuration) {
            float amt = deltaTime/(float) mResizeBufferDuration;amt = mResizeInterpolator.getInterpolation(amt);animating = true;resizeAlpha = 255 - (int)(amt*255);} else {
            disposeResizeBuffer();}
    }
//获取mDirty,该值表示需要重绘的区域
 final Rect dirty = mDirty;if (mSurfaceHolder != null) {
        // The app owns the surface, we won't draw.
        dirty.setEmpty();if (animating) {
            if (mScroller != null) {
                mScroller.abortAnimation();}
            disposeResizeBuffer();}
        return;}
//如果fullRedrawNeeded为真,则把dirty区域置为整个屏幕,表示整个视图都需要绘制
//第一次绘制流程,需要绘制所有视图
if (fullRedrawNeeded) {
    mAttachInfo.mIgnoreDirtyState = true;dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
}

if (DEBUG_ORIENTATION || DEBUG_DRAW) {
    Log.v(TAG, "Draw " + mView + "/"
            + mWindowAttributes.getTitle()
            + ": dirty={" + dirty.left + "," + dirty.top
            + "," + dirty.right + "," + dirty.bottom + "} surface="
            + surface + " surface.isValid()=" + surface.isValid() + ", appScale:" +appScale + ", width=" + mWidth + ", height=" + mHeight);
}

mAttachInfo.mTreeObserver.dispatchOnDraw();int xOffset = 0;
int yOffset = curScrollY;
final WindowManager.LayoutParams params = mWindowAttributes;
final Rect surfaceInsets = params != null ? params.surfaceInsets : null;
if (surfaceInsets != null) {
    xOffset -= surfaceInsets.left;yOffset -= surfaceInsets.top;// Offset dirty rect for surface insets.
    dirty.offset(surfaceInsets.left, surfaceInsets.right);
}

boolean accessibilityFocusDirty = false;
final Drawable drawable = mAttachInfo.mAccessibilityFocusDrawable;
if (drawable != null) {
    final Rect bounds = mAttachInfo.mTmpInvalRect;final boolean hasFocus = getAccessibilityFocusedRect(bounds);if (!hasFocus) {
        bounds.setEmpty();}
    if (!bounds.equals(drawable.getBounds())) {
        accessibilityFocusDirty = true;}
}

mAttachInfo.mDrawingTime =mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
    if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
        // If accessibility focus moved, always invalidate the root.
        boolean invalidateRoot = accessibilityFocusDirty;// Draw with hardware renderer.
        mIsAnimating = false;if (mHardwareYOffset != yOffset || mHardwareXOffset != xOffset) {
            mHardwareYOffset = yOffset;mHardwareXOffset = xOffset;invalidateRoot = true;}
        mResizeAlpha = resizeAlpha;if (invalidateRoot) {
            mAttachInfo.mHardwareRenderer.invalidateRoot();}

        dirty.setEmpty();mBlockResizeBuffer = false;mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);} else {
        // If we get here with a disabled & requested hardware renderer, something went
        // wrong (an invalidate posted right before we destroyed the hardware surface
        // for instance) so we should just bail out. Locking the surface with software
        // rendering at this point would lock it forever and prevent hardware renderer
        // from doing its job when it comes back.
        // Before we request a new frame we must however attempt to reinitiliaze the
        // hardware renderer if it's in requested state. This would happen after an
        // eglTerminate() for instance.
        if (mAttachInfo.mHardwareRenderer != null &&!mAttachInfo.mHardwareRenderer.isEnabled() &&mAttachInfo.mHardwareRenderer.isRequested()) {

            try {
                mAttachInfo.mHardwareRenderer.initializeIfNeeded(
                        mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);} catch (OutOfResourcesException e) {
                handleOutOfResourcesException(e);return;}

            mFullRedrawNeeded = true;scheduleTraversals();return;}

        if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
            return;}
    }
}

if (animating) {
    mFullRedrawNeeded = true;scheduleTraversals();
}
这里我们只看标注的红色代码,首先是先获取了mDirty值,该值保存了需要重绘的区域的信息。接着根据fullRedrawNeeded来判断是否需要重置dirty区域,最后调用了ViewRootImpl----->drawSoftware方法,并把相关参数传递进去,包括dirty区域,我们接着看该方法的源码:
/**
 * @return true if drawing was successful, false if an error occurred
 * 如果绘图成功则为true,如果出现错误,则为false
 */
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;}

        // TODO: Do this in native
        canvas.setDensity(mDensity);} catch (Surface.OutOfResourcesException e) {
        handleOutOfResourcesException(e);return false;} catch (IllegalArgumentException e) {
        Log.e(TAG, "Could not lock surface", e);// Don't assume this is due to out of memory, it could be
        // something else, and if it is something else then we could
        // kill stuff (or ourself) for no reason.
        mLayoutRequested = true;    // ask wm for a new surface next time.
        return false;}

    try {
        if (DEBUG_ORIENTATION || DEBUG_DRAW) {
            Log.v(TAG, "Surface " + surface + " drawing to bitmap w="
                    + canvas.getWidth() + ", h=" + canvas.getHeight());//canvas.drawARGB(255, 255, 0, 0);
        }

        // If this bitmap's format includes an alpha channel, we
        // need to clear it before drawing so that the child will
        // properly re-composite its drawing on a transparent
        // background. This automatically respects the clip/dirty region
        // or
        // If we are applying an offset, we need to clear the area
        // where the offset doesn't appear to avoid having garbage
        // left in the blank areas.
        if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
            canvas.drawColor(0, PorterDuff.Mode.CLEAR);}

        dirty.setEmpty();mIsAnimating = false;mView.mPrivateFlags |= View.PFLAG_DRAWN;if (DEBUG_DRAW) {
            Context cxt = mView.getContext();Log.i(TAG, "Drawing: package:" + cxt.getPackageName() +", metrics=" + cxt.getResources().getDisplayMetrics() +", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());}
        try {
            canvas.translate(-xoff, -yoff);if (mTranslator != null) {
                mTranslator.translateCanvas(canvas);}
            canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);attachInfo.mSetIgnoreDirtyState = false;  //正式开始绘制
            mView.draw(canvas);
drawAccessibilityFocusedDrawableIfNeeded(canvas);} finally {
            if (!attachInfo.mSetIgnoreDirtyState) {
                // Only clear the flag if it was not set during the mView.draw() call
                attachInfo.mIgnoreDirtyState = false;}
        }
    } finally {
        try {
            surface.unlockCanvasAndPost(canvas);} catch (IllegalArgumentException e) {
            Log.e(TAG, "Could not unlock surface", e);mLayoutRequested = true;    // ask wm for a new surface next time.
            //noinspection ReturnInsideFinallyBlock
            return false;}

        if (LOCAL_LOGV) {
            Log.v(TAG, "Surface " + surface + " unlockCanvasAndPost");}
    }
    return true;
}
从上面代码可以看出,首先是实例化了Canvas对象,然后锁定该canvas的区域,由dirty区域决定,接着对canvas进行一系列的属性赋值,最后调用了mView.draw(canvas)方法,那么之前就讲过这里的mView就是我们的DectorView所以是从DectorView顶层开始绘制 那么之前的一切都是在进行准备一块画板具体的绘制实在mView.draw当中,这里将画板给入,而现在则是正式开始绘制流程。

View的绘制

由于ViewGroup没有重写draw方法,因此所有的View都是调用View----->draw方法,因此,我们直接看它的源码:

/**
 * Manually render this view (and all of its children) to the given Canvas.
 * The view must have already done a full layout before this function is
 * called.  When implementing a view, implement
 * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
 * If you do need to override this method, call the superclass version.
 *@param 画布是视图呈现的画布。
 * @param canvas The Canvas to which the View is rendered.
 *               Opaque 不透明物
 */
@CallSuper
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  对View的背景进行绘制
     *      2. If necessary, save the canvas' layers to prepare for fading  保存当前的图层信息
     *      3. Draw view's content 绘制View的内容
     *      4. Draw children 对View的子View进行绘制(如果有子View)
     *      5. If necessary, draw the fading edges and restore layers 绘制View的褪色的边缘,类似于阴影效果
     *      6. Draw decorations (scrollbars for instance) 绘制View的装饰(例如:滚动条)
     */

    // 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);// Step 4, draw the children
        dispatchDraw(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);// we're done...
        return;}
.................
.................

可以看到,draw过程比较复杂,但是逻辑十分清晰,而官方注释也清楚地说明了每一步的做法。我们首先来看一开始的标记位dirtyOpaque,该标记位的作用是判断当前View是否是透明的,如果View是透明的,那么根据下面的逻辑可以看出,将不会执行一些步骤,比如绘制背景、绘制内容等。这样很容易理解,因为一个View既然是透明的,那就没必要绘制它了。接着是绘制流程的六个步骤,接下来讲。

绘制流程的六个步骤:上面红色注释已经标记过

1.绘制背景:

/**
 * 将背景画在指定的画布上 onto:在什么......之上
 * Draws the background onto the specified canvas.
 *
 * @param canvas Canvas on which to draw the background
 */
private void drawBackground(Canvas canvas) {
    final Drawable background = mBackground;if (background == null) {
        return;}

    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;}
    }

    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);}
}
// 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;

2.绘制内容:

/**
 * Implement this to do your drawing.
 *
 * @param canvas the canvas on which the background will be drawn
 */
protected void onDraw(Canvas canvas) {
}

View中该方法是一个空实现,因为不同的View有着不同的内容,这需要我们自己去实现,即在自定义View中重写该方法来实现。

3.绘制子view:

如果当前的View是一个ViewGroup类型,那么就需要绘制它的子View,这里调用了dispatchDraw,而View中该方法是空实现,实际是ViewGroup重写了这个方法,那么我们来看看,ViewGroup------->dispatchDraw源码:

/**
 * {@inheritDoc}
 */
@Override
protected void dispatchDraw(Canvas canvas) {
    boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);final int childrenCount = mChildrenCount;final View[] children = mChildren;int flags = mGroupFlags;if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
        final boolean buildCache = !isHardwareAccelerated();for (int i = 0; i < childrenCount; i++) {
            final View child = children[i];if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
                final LayoutParams params = child.getLayoutParams();attachLayoutAnimationParameters(child, params, i, childrenCount);bindLayoutAnimation(child);}
        }

        final LayoutAnimationController controller = mLayoutAnimationController;if (controller.willOverlap()) {
            mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;}

        controller.start();mGroupFlags &= ~FLAG_RUN_ANIMATION;mGroupFlags &= ~FLAG_ANIMATION_DONE;if (mAnimationListener != null) {
            mAnimationListener.onAnimationStart(controller.getAnimation());}
    }

    int clipSaveCount = 0;final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;if (clipToPadding) {
        clipSaveCount = canvas.save();canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,mScrollX + mRight - mLeft - mPaddingRight,mScrollY + mBottom - mTop - mPaddingBottom);}

    // We will draw our child's animation, let's reset the flag
    mPrivateFlags &= ~PFLAG_DRAW_ANIMATION;mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;boolean more = false;final long drawingTime = getDrawingTime();if (usingRenderNodeProperties) canvas.insertReorderBarrier();final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();int transientIndex = transientCount != 0 ? 0 : -1;// Only use the preordered list if not HW accelerated, since the HW pipeline will do the
    // draw reordering internally
    final ArrayList<View> preorderedList = usingRenderNodeProperties? null : buildOrderedChildList();final boolean customOrder = preorderedList == null
            && isChildrenDrawingOrderEnabled();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;}
        }
        int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;final View child = (preorderedList == null)
                ? children[childIndex] : preorderedList.get(childIndex);if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
            more |= drawChild(canvas, child, drawingTime);}
    }
    while (transientIndex >= 0) {
        // there may be additional transient views after the normal views
        final View transientChild = mTransientViews.get(transientIndex);if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||transientChild.getAnimation() != null) {
            more |= drawChild(canvas, transientChild, drawingTime);}
        transientIndex++;if (transientIndex >= transientCount) {
            break;}
    }
    if (preorderedList != null) preorderedList.clear();// Draw any disappearing views that have animations
    if (mDisappearingChildren != null) {
        final ArrayList<View> disappearingChildren = mDisappearingChildren;final int disappearingCount = disappearingChildren.size() - 1;// Go backwards -- we may delete as animations finish
        for (int i = disappearingCount; i >= 0; i--) {
            final View child = disappearingChildren.get(i);more |= drawChild(canvas, child, drawingTime);}
    }
    if (usingRenderNodeProperties) canvas.insertInorderBarrier();if (debugDraw()) {
        onDebugDraw(canvas);}

    if (clipToPadding) {
        canvas.restoreToCount(clipSaveCount);}

    // mGroupFlags might have been updated by drawChild()
    flags = mGroupFlags;if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {
        invalidate(true);}

    if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&mLayoutAnimationController.isDone() && !more) {
        // We want to erase the drawing cache and notify the listener after the
        // next frame is drawn because one extra invalidate() is caused by
        // drawChild() after the animation is over
        mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;final Runnable end = new Runnable() {
           public void run() {
               notifyAnimationListener();}
        };post(end);}
}

里面的代码主要遍历了所有子View,每个子View都调用了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.
 *画一个这个视图组的孩子。这个方法负责
  获得画布在正确的状态。这包括剪切,转化
  孩子的滚动原点在0 0,应用任何动画
 *转换。
 * @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进行绘制。

4.绘制装饰:

/**
 * 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);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);}
}

注:所谓的绘制装饰,就是指View除了背景、内容、子View的其余部分,绘制例如滚动条,滚动指示器等

那么,到目前为止,View的绘制流程也讲述完毕了

Android UI绘制流程源码详细讲解Draw(Canvas canvas)相关推荐

  1. Doris FE启动流程源码详细解析

    Doris FE启动流程源码详细解析 一.简介 Apache Doris是一个现代化的MPP分析型数据库产品.仅需亚秒级响应时间即可获得查询结果,有效地支持实时数据分析.Apache Doris的分布 ...

  2. Android之View绘制流程源码分析

    版权声明:本文出自汪磊的博客,转载请务必注明出处. 对于稍有自定义View经验的安卓开发者来说,onMeasure,onLayout,onDraw这三个方法都不会陌生,起码多少都有所接触吧. 在安卓中 ...

  3. Android电话拨打流程源码分析

    前面分析了电话拨号界面及电话呼叫界面,由于Android的电话Phone设计的很复杂,因此先从UI层入手分析.想要了解Android的电话拨号UI,请查看Android电话拨号UI分析,电话拨号UI在 ...

  4. Android 电话博大流程源码分析

    前面分析了电话拨号界面及电话呼叫界面,由于Android的电话Phone设计的很复杂,因此先从UI层入手分析.想要了解Android的电话拨号UI,请查看Android电话拨号UI分析,电话拨号UI在 ...

  5. Android平台类加载流程源码分析

    前言 其实大家都知道的Android是使用Java作为开发语言,但是他使用的虚拟机却并不是传统的JVM,在4.4以前Android使用Dalvik虚拟机,之后使用ART(Android Runtime ...

  6. Android Launcher启动应用程序流程源码解析

    带着问题看源码 点击桌面Launcher图标后做了哪些工作? 应用程序什么时候被创建的? Application和MainActivity的onCreate()方法什么时候被调用的? 概述 在Andr ...

  7. Activity启动流程源码分析(基于Android N)

    Activity启动流程源码分析 一个Activity启动分为两种启动方式,一种是从Launcher界面上的图标点击启动,另一种是从一个Activity中设置按钮点击启动另外一个Activity.这里 ...

  8. android系统加载主题的流程,详解Android布局加载流程源码

    一.首先看布局层次 看这么几张图 我们会发现DecorView里面包裹的内容可能会随着不同的情况而变化,但是在Decor之前的层次关系都是固定的.即Activity包裹PhoneWindow,Phon ...

  9. Android音频框架之二 用户录音启动流程源码走读

    前言 此篇是对<Android音频框架之一 详解audioPolicy流程及HAL驱动加载>的延续,此系列博文是记录在Android7.1系统即以后版本实现 内录音功能. 当用户使用 Au ...

  10. Activity启动流程源码分析-浅析生命周期函数

    源码分析 接着上一篇 Activity启动流程源码分析-setContentView源码阅读 的讲解,本节介绍一下Activity的生命周期函数何时被调用 要看Activity的生命周期函数何时被调用 ...

最新文章

  1. 解决YUM下Loaded plugins: fastestmirror Determining fastest mirrors 的问题
  2. 查看本地计算机已安装HOTFIX 几种方法
  3. val什么意思vb中的属性值_老司机带你探索Mysql中int(1)、int(10)、int(11)的区别是什么?...
  4. sql统计系统时间那一个月数量_关于BE00007图书借阅管理系统bug修复总结
  5. Oracle案例:一次非常艰难的drop多个PDB的恢复
  6. spring-retry小结
  7. Mac技巧,更改Mac桌面图片
  8. 可视化编辑json数据——json editor
  9. Word设置标题以及自动编号——保姆级教程
  10. 计算机科学论文生成器,软件自动生成假论文:满篇废话
  11. 利用R语言画简单时间序列图
  12. 修改计算机参数,缺氧参数怎么修改 游戏内参数修改方法解答
  13. 四轴飞行器的串级PID参数整定经验
  14. [免费专栏] Android安全之Android Fragment注入
  15. 嵌入式软件工程师就业方向有哪些呢?
  16. sqrt()函数详解
  17. 三生三世十里桃花用计算机怎么弄,三生三世十里桃花ios如何用电脑玩 三生三世十里桃花ios模拟器教程...
  18. Android实现记事本功能
  19. 使用QT设计师界面类创建2个界面,通过按键从界面1切换到界面2
  20. 如何清除谷歌浏览器缓存

热门文章

  1. 服务器装了无线网卡失败,.NET Core Runtime安装失败0x80070005Error报错服务器原因
  2. php 0x80070005,FastCGI Error Number: 5 (0x80070005)解决方法
  3. windows mingw 64,SDL ,devil,glfw,opengl,qt环境搭建
  4. 孩子给产品经理的一堂课
  5. 怎么保存html,怎么保存整个网页,教你一个妙招就可以搞定!
  6. 时间漩涡的世界 (一)
  7. 搜狗皮肤.php,搜狗皮肤制作
  8. numpy中的revel和flatten
  9. java 临时文件_在Java中使用临时文件/文件夹
  10. python的encode()和decode()的用法及实例