View体系(十一)View的draw流程

1,171

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

之前的文章《View体系(六)View工作流程入口》提到View的工作流程包括了measurelayoutdraw的过程,今天我们就来看一下Viewdraw流程是怎样的。

(注:文中源码基于 Android 12

Viewdraw流程很简单,源码里的注释官方也写的很清楚,我们看Viewdraw方法:

    public void draw(Canvas canvas) {
        ...
        // Step 1, draw the background, if needed
        drawBackground(canvas);
        ...
        // skip step 2 & 5 if possible (common case)
        ...
        // Step 2, save the canvas' layers
        ...
        // Step 3, draw the content
        onDraw(canvas);
        ...
        // Step 4, draw the children
        dispatchDraw(canvas);
        ...
        // Step 5, draw the fade effect and restore layers
        ...
        // Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);
        ...
        // Step 7, draw the default focus highlight
        drawDefaultFocusHighlight(canvas);

总览

官方注释已经清楚的写了每一步的工作:

  1. 如果需要,则绘制背景(drawBackground
  2. 保存当前canvas层
  3. 绘制View的内容(onDraw
  4. 绘制子View(dispatchDraw
  5. 如果需要,则绘制View的褪色边缘,类似于阴影效果
  6. 绘制装饰,比如滚动条(onDrawForeground
  7. 绘制默认焦点高亮效果(drawDefaultFocusHighlight

注释中说明了第2步和第5步可以跳过,这里就不展开讲解,在此重点分析其他步骤。

步骤1:绘制背景

进入ViewdrawBackground方法:

    private void drawBackground(Canvas canvas) {
        final Drawable background = mBackground;
        ...
        setBackgroundBounds();  //1
        ...
        background.draw(canvas);    //2

注释1处设置背景范围,注释2处通过Drawabledraw方法来绘制背景,关于Drawable将在后面的文章详细讲解。看注释1的setBackgroundBounds是如何设置背景范围的:

    void setBackgroundBounds() {
        if (mBackgroundSizeChanged && mBackground != null) {
            mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop);    //1
            mBackgroundSizeChanged = false;
            rebuildOutline();
        }
    }

看到注释1处通过ViewmRight、mLeft、mBottom、mTop等参数调用mBackground.setBounds方法来进行绘制范围的设置。

步骤2:保存当前canvas层

步骤3:绘制View的内容

步骤3调用了ViewonDraw方法,这个方法是一个空实现,因为不同的View有不同的内容,所以需要我们自己去实现,即在自定义View时重写该方法来实现我们自己的绘制。

    protected void onDraw(Canvas canvas) {
    }

步骤4:绘制子View

步骤4调用了dispatchDraw方法,这个方法也是个空实现:

    protected void dispatchDraw(Canvas canvas) {

    }

ViewGroup重写了这个方法,我们看ViewGroupdispatchDraw方法:

    protected void dispatchDraw(Canvas canvas) {
        ...
        for (int i = 0; i < childrenCount; i++) {
            ...
            more |= drawChild(canvas, transientChild, drawingTime);

源码很长,这里只贴出关键代码,在dispatchDraw方法中遍历子View并调用drawChild方法,我们继续看drawChild方法:

    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);
    }

drawChild方法实际就是调用了子Viewdraw方法对子View进行绘制

步骤5:绘制View的阴影效果

步骤6:绘制装饰

步骤6调用了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);
        }
    }

这里主要是对ScrollBar及其它装饰进行绘制。

步骤7:绘制默认焦点高亮效果

    private void drawDefaultFocusHighlight(Canvas canvas) {
        if (mDefaultFocusHighlight != null && isFocused()) {
            if (mDefaultFocusHighlightSizeChanged) {
                mDefaultFocusHighlightSizeChanged = false;
                final int l = mScrollX;
                final int r = l + mRight - mLeft;
                final int t = mScrollY;
                final int b = t + mBottom - mTop;
                mDefaultFocusHighlight.setBounds(l, t, r, b);
            }
            mDefaultFocusHighlight.draw(canvas);
        }
    }

学习更多知识,请关注我的个人博客:droidYu